feat: allow custom install and start commands (#892)

# Description

Gives the ability to define an `install` and `startup` command when
importing a project, so we can work on a project locally without any
issue.

# Preview

<img width="2256" height="1422" alt="image"
src="https://github.com/user-attachments/assets/2132b1cb-5f71-4b88-84db-8ecc81cf1f66"
/>

---------

Co-authored-by: Will Chen <willchen90@gmail.com>
This commit is contained in:
Olyno
2025-08-18 19:41:22 +02:00
committed by GitHub
parent f72157a443
commit 237017acd9
10 changed files with 693 additions and 13 deletions

View File

@@ -27,6 +27,12 @@ import {
import { selectedAppIdAtom } from "@/atoms/appAtoms";
import { useSetAtom } from "jotai";
import { useLoadApps } from "@/hooks/useLoadApps";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "./ui/accordion";
interface ImportAppDialogProps {
isOpen: boolean;
@@ -39,6 +45,8 @@ export function ImportAppDialog({ isOpen, onClose }: ImportAppDialogProps) {
const [customAppName, setCustomAppName] = useState<string>("");
const [nameExists, setNameExists] = useState<boolean>(false);
const [isCheckingName, setIsCheckingName] = useState<boolean>(false);
const [installCommand, setInstallCommand] = useState("pnpm install");
const [startCommand, setStartCommand] = useState("pnpm dev");
const navigate = useNavigate();
const { streamMessage } = useStreamChat({ hasChatId: false });
const { refreshApps } = useLoadApps();
@@ -89,6 +97,8 @@ export function ImportAppDialog({ isOpen, onClose }: ImportAppDialogProps) {
return IpcClient.getInstance().importApp({
path: selectedPath,
appName: customAppName,
installCommand: installCommand || undefined,
startCommand: startCommand || undefined,
});
},
onSuccess: async (result) => {
@@ -128,6 +138,8 @@ export function ImportAppDialog({ isOpen, onClose }: ImportAppDialogProps) {
setHasAiRules(null);
setCustomAppName("");
setNameExists(false);
setInstallCommand("pnpm install");
setStartCommand("pnpm dev");
};
const handleAppNameChange = async (
@@ -140,6 +152,10 @@ export function ImportAppDialog({ isOpen, onClose }: ImportAppDialogProps) {
}
};
const hasInstallCommand = installCommand.trim().length > 0;
const hasStartCommand = startCommand.trim().length > 0;
const commandsValid = hasInstallCommand === hasStartCommand;
return (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent>
@@ -221,6 +237,41 @@ export function ImportAppDialog({ isOpen, onClose }: ImportAppDialogProps) {
</div>
</div>
<Accordion type="single" collapsible>
<AccordionItem value="advanced-options">
<AccordionTrigger className="text-sm hover:no-underline">
Advanced options
</AccordionTrigger>
<AccordionContent className="space-y-4">
<div className="grid gap-2">
<Label className="text-sm ml-2 mb-2">
Install command
</Label>
<Input
value={installCommand}
onChange={(e) => setInstallCommand(e.target.value)}
placeholder="pnpm install"
disabled={importAppMutation.isPending}
/>
</div>
<div className="grid gap-2">
<Label className="text-sm ml-2 mb-2">Start command</Label>
<Input
value={startCommand}
onChange={(e) => setStartCommand(e.target.value)}
placeholder="pnpm dev"
disabled={importAppMutation.isPending}
/>
</div>
{!commandsValid && (
<p className="text-sm text-red-500">
Both commands are required when customizing.
</p>
)}
</AccordionContent>
</AccordionItem>
</Accordion>
{hasAiRules === false && (
<Alert className="border-yellow-500/20 text-yellow-500 flex items-start gap-2">
<TooltipProvider>
@@ -264,7 +315,10 @@ export function ImportAppDialog({ isOpen, onClose }: ImportAppDialogProps) {
<Button
onClick={handleImport}
disabled={
!selectedPath || importAppMutation.isPending || nameExists
!selectedPath ||
importAppMutation.isPending ||
nameExists ||
!commandsValid
}
className="min-w-[80px]"
>