Skip to content

Commit 07f811a

Browse files
authored
Better bun command api (#1351)
* Update run bun command api
1 parent fbffedc commit 07f811a

File tree

10 files changed

+72
-128
lines changed

10 files changed

+72
-128
lines changed

apps/studio/electron/main/bun/index.ts

Lines changed: 25 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1+
import type { RunBunCommandOptions, RunBunCommandResult } from '@onlook/models';
12
import { exec } from 'child_process';
23
import { app } from 'electron';
34
import path from 'path';
5+
import { promisify } from 'util';
46
import { __dirname } from '../index';
57
import { replaceCommand } from './parse';
68

9+
const execAsync = promisify(exec);
10+
711
export const getBunExecutablePath = (): string => {
812
const arch = process.arch === 'arm64' ? 'aarch64' : process.arch;
913
const isProduction = app.isPackaged;
@@ -16,60 +20,29 @@ export const getBunExecutablePath = (): string => {
1620
return bunPath;
1721
};
1822

19-
export interface RunBunCommandOptions {
20-
cwd: string;
21-
env?: NodeJS.ProcessEnv;
22-
callbacks?: {
23-
onStdout?: (data: string) => void;
24-
onStderr?: (data: string) => void;
25-
onClose?: (code: number | null, signal: string | null) => void;
26-
onError?: (err: Error) => void;
27-
};
28-
}
29-
30-
export const runBunCommand = (
23+
export async function runBunCommand(
3124
command: string,
3225
options: RunBunCommandOptions,
33-
): Promise<{ stdout: string; stderr: string }> => {
34-
const commandToExecute = getBunCommand(command);
35-
const shell = process.platform === 'win32' ? 'powershell.exe' : 'bash';
36-
37-
return new Promise((resolve, reject) => {
38-
exec(
39-
commandToExecute,
40-
{
41-
cwd: options.cwd,
42-
env: {
43-
...options.env,
44-
...process.env,
45-
},
46-
maxBuffer: 1024 * 1024 * 10,
47-
shell,
48-
},
49-
(error: Error | null, stdout: string, stderr: string) => {
50-
// Call the callbacks with the complete output
51-
if (stdout && options.callbacks?.onStdout) {
52-
options.callbacks.onStdout(stdout);
53-
}
54-
55-
if (stderr && options.callbacks?.onStderr) {
56-
options.callbacks.onStderr(stderr);
57-
}
58-
59-
if (error) {
60-
options.callbacks?.onError?.(error);
61-
reject(
62-
new Error(`Process exited with error: ${error.message}\nStderr: ${stderr}`),
63-
);
64-
return;
65-
}
66-
67-
options.callbacks?.onClose?.(0, null);
68-
resolve({ stdout, stderr });
69-
},
70-
);
71-
});
72-
};
26+
): Promise<RunBunCommandResult> {
27+
try {
28+
const commandToExecute = getBunCommand(command);
29+
const shell = process.platform === 'win32' ? 'powershell.exe' : '/bin/sh';
30+
31+
console.log('Executing command: ', commandToExecute, options.cwd);
32+
const { stdout, stderr } = await execAsync(commandToExecute, {
33+
cwd: options.cwd,
34+
maxBuffer: 1024 * 1024 * 10,
35+
env: options.env,
36+
shell,
37+
});
38+
39+
console.log('Command executed with output: ', stdout);
40+
return { success: true, output: stdout.toString(), error: stderr.toString() };
41+
} catch (error) {
42+
console.error(error);
43+
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
44+
}
45+
}
7346

7447
export const getBunCommand = (command: string): string => {
7548
const bunExecutable = getBunExecutablePath();

apps/studio/electron/main/create/install.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,12 @@ export async function createProject(
2525
// Install dependencies
2626
const result = await runBunCommand('npm install -y --no-audit --no-fund', {
2727
cwd: fullPath,
28-
callbacks: {
29-
onStdout: (data) =>
30-
onProgress(
31-
CreateStage.INSTALLING,
32-
'Installing dependencies. This may take a few minutes...',
33-
),
34-
},
3528
});
3629

30+
if (!result.success) {
31+
throw new Error(`Failed to install dependencies: ${result.error}`);
32+
}
33+
3734
onProgress(CreateStage.COMPLETE, 'Project created successfully!');
3835
} catch (error) {
3936
onProgress(CreateStage.ERROR, `Project creation failed: ${error}`);

apps/studio/electron/main/create/setup.ts

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,15 @@ export const installProjectDependencies = async (
88
): Promise<void> => {
99
try {
1010
onProgress(SetupStage.INSTALLING, 'Installing required packages...');
11-
runBunCommand(installCommand, {
11+
const result = await runBunCommand(installCommand, {
1212
cwd: targetPath,
13-
callbacks: {
14-
onStdout: (data) => onProgress(SetupStage.INSTALLING, data),
15-
onStderr: (data) => onProgress(SetupStage.INSTALLING, data),
16-
onClose: (code, signal) => {
17-
if (code !== 0) {
18-
onProgress(
19-
SetupStage.ERROR,
20-
`Failed to install dependencies. Code: ${code}, Signal: ${signal}`,
21-
);
22-
} else {
23-
onProgress(SetupStage.COMPLETE, 'Project dependencies installed.');
24-
}
25-
},
26-
},
2713
});
14+
15+
if (!result.success) {
16+
throw new Error(`Failed to install dependencies: ${result.error}`);
17+
}
18+
19+
onProgress(SetupStage.COMPLETE, 'Project dependencies installed.');
2820
} catch (err) {
2921
console.error(err);
3022
onProgress(

apps/studio/electron/main/events/create.ts

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import type { ImageMessageContext } from '@onlook/models/chat';
33
import { MainChannels } from '@onlook/models/constants';
44
import { ipcMain } from 'electron';
55
import { mainWindow } from '..';
6-
import { runBunCommand } from '../bun';
76
import projectCreator from '../create';
87
import { createProject } from '../create/install';
8+
import { installProjectDependencies } from '../create/setup';
99

1010
export function listenForCreateMessages() {
1111
ipcMain.handle(MainChannels.CREATE_NEW_PROJECT, (e: Electron.IpcMainInvokeEvent, args) => {
@@ -30,26 +30,7 @@ export function listenForCreateMessages() {
3030
});
3131
};
3232
const { folderPath, installCommand } = args;
33-
return runBunCommand(installCommand, {
34-
cwd: folderPath,
35-
callbacks: {
36-
onStdout: (data) => progressCallback(SetupStage.CONFIGURING, data),
37-
onStderr: (data) => progressCallback(SetupStage.CONFIGURING, data),
38-
onClose: (code, signal) => {
39-
if (code !== 0) {
40-
progressCallback(
41-
SetupStage.ERROR,
42-
`Failed to install dependencies. Code: ${code}, Signal: ${signal}`,
43-
);
44-
} else {
45-
progressCallback(
46-
SetupStage.COMPLETE,
47-
'Project dependencies installed.',
48-
);
49-
}
50-
},
51-
},
52-
});
33+
return installProjectDependencies(folderPath, installCommand, progressCallback);
5334
},
5435
);
5536

apps/studio/electron/main/hosting/helpers.ts

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { addNextBuildConfig } from '@onlook/foundation';
2+
import type { RunBunCommandResult } from '@onlook/models';
23
import { CUSTOM_OUTPUT_DIR } from '@onlook/models/constants';
34
import {
45
appendFileSync,
@@ -124,28 +125,13 @@ export function copyDir(src: string, dest: string) {
124125
}
125126
}
126127

127-
export function runBuildScript(
128+
export async function runBuildScript(
128129
folderPath: string,
129130
buildScript: string,
130-
): Promise<{
131-
success: boolean;
132-
error?: string;
133-
}> {
134-
return new Promise((resolve, reject) => {
135-
runBunCommand(buildScript, {
136-
cwd: folderPath,
137-
env: { ...process.env, NODE_ENV: 'production' },
138-
callbacks: {
139-
onClose: (code, signal) => {
140-
console.log(`Build script closed with code ${code} and signal ${signal}`);
141-
if (code === 0) {
142-
resolve({ success: true });
143-
} else {
144-
resolve({ success: false, error: 'Build script failed' });
145-
}
146-
},
147-
},
148-
});
131+
): Promise<RunBunCommandResult> {
132+
return await runBunCommand(buildScript, {
133+
cwd: folderPath,
134+
env: { ...process.env, NODE_ENV: 'production' },
149135
});
150136
}
151137

apps/studio/electron/main/hosting/index.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,14 +117,17 @@ class HostingManager {
117117
console.log('Skipping build');
118118
return;
119119
}
120-
const { success: buildSuccess, error: buildError } = await runBuildScript(
121-
folderPath,
122-
BUILD_SCRIPT_NO_LINT,
123-
);
120+
const {
121+
success: buildSuccess,
122+
error: buildError,
123+
output: buildOutput,
124+
} = await runBuildScript(folderPath, BUILD_SCRIPT_NO_LINT);
124125

125126
if (!buildSuccess) {
126127
this.emitState(HostingStatus.ERROR, `Build failed with error: ${buildError}`);
127128
throw new Error(`Build failed with error: ${buildError}`);
129+
} else {
130+
console.log('Build succeeded with output: ', buildOutput);
128131
}
129132
}
130133

apps/studio/electron/main/run/terminal.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,10 @@ class TerminalManager {
2323

2424
create(id: string, options?: { cwd?: string }): boolean {
2525
try {
26-
const shell = os.platform() === 'win32' ? 'powershell.exe' : 'bash';
26+
const shell = os.platform() === 'win32' ? 'powershell.exe' : '/bin/sh';
2727
const ptyProcess = pty.spawn(shell, [], {
2828
name: 'xterm-color',
29-
cwd: options?.cwd ?? process.env.HOME,
30-
env: process.env,
29+
cwd: options?.cwd,
3130
});
3231

3332
ptyProcess.onData((data: string) => {

apps/studio/src/routes/editor/EditPanel/ChatTab/CodeChangeDisplay/BashCodeDisplay.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useProjectsManager } from '@/components/Context';
22
import { invokeMainChannel } from '@/lib/utils';
3+
import type { RunBunCommandResult } from '@onlook/models';
34
import { MainChannels } from '@onlook/models/constants';
45
import { Button } from '@onlook/ui/button';
56
import { Icons } from '@onlook/ui/icons';
@@ -20,18 +21,18 @@ const BashCodeDisplay = observer(
2021
return;
2122
}
2223
setRunning(true);
23-
const res: { stdout: string; stderr: string } | null = await invokeMainChannel(
24+
const res: RunBunCommandResult | null = await invokeMainChannel(
2425
MainChannels.RUN_COMMAND,
2526
{
2627
cwd: projectPath,
2728
command: content,
2829
},
2930
);
30-
if (!res) {
31-
setStdErr('Failed to run command');
31+
if (!res || !res.success) {
32+
setStdErr(res?.error || 'Failed to run command');
3233
} else {
33-
setStdOut(res.stdout);
34-
setStdErr(res.stderr);
34+
setStdOut(res.output || '');
35+
setStdErr(null);
3536
}
3637
setRunning(false);
3738
};

packages/models/src/bun/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export interface RunBunCommandOptions {
2+
cwd: string;
3+
env?: NodeJS.ProcessEnv;
4+
}
5+
6+
export interface RunBunCommandResult {
7+
success: boolean;
8+
error?: string;
9+
output?: string;
10+
}

packages/models/src/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
export * from './actions/';
2+
export * from './bun/';
23
export * from './chat/';
34
export * from './code/';
45
export * from './constants/';
56
export * from './create/';
67
export * from './editor/';
78
export * from './element/';
89
export * from './ide/';
10+
export * from './pages/';
911
export * from './projects/';
12+
export * from './run/';
1013
export * from './settings/';
11-
export * from './pages/';

0 commit comments

Comments
 (0)