Skip to content

Commit

Permalink
Install npm packages after plan ran. External link option (#410)
Browse files Browse the repository at this point in the history
* Install npm packages after plan ran. External link option

* Remove useless comment

* Address comments
  • Loading branch information
nichochar authored Oct 23, 2024
1 parent 6803e6f commit 7f94377
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 34 deletions.
9 changes: 9 additions & 0 deletions packages/api/ai/plan-parser.mts
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,12 @@ export async function parsePlan(
throw new Error('Failed to parse XML response');
}
}

export function getPackagesToInstall(plan: Plan): string[] {
return plan.actions
.filter(
(action): action is NpmInstallCommand =>
action.type === 'command' && action.command === 'npm install',
)
.flatMap((action) => action.packages);
}
33 changes: 19 additions & 14 deletions packages/api/apps/app.mts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { asc, desc, eq } from 'drizzle-orm';
import { npmInstall } from '../exec.mjs';
import { generateApp } from '../ai/generate.mjs';
import { toValidPackageName } from '../apps/utils.mjs';
import { parsePlan } from '../ai/plan-parser.mjs';
import { getPackagesToInstall, parsePlan } from '../ai/plan-parser.mjs';

function toSecondsSinceEpoch(date: Date): number {
return Math.floor(date.getTime() / 1000);
Expand Down Expand Up @@ -54,19 +54,24 @@ export async function createAppWithAi(data: CreateAppWithAiSchemaType): Promise<
const plan = await parsePlan(result, app, data.prompt, randomid());
await applyPlan(app, plan);

// Run npm install again since we don't have a good way of parsing the plan to know if we should...
npmInstall({
cwd: pathToApp(app.externalId),
stdout(data) {
console.log(data.toString('utf8'));
},
stderr(data) {
console.error(data.toString('utf8'));
},
onExit(code) {
console.log(`npm install exit code: ${code}`);
},
});
const packagesToInstall = getPackagesToInstall(plan);

if (packagesToInstall.length > 0) {
console.log('installing packages', packagesToInstall);
npmInstall({
cwd: pathToApp(app.externalId),
packages: packagesToInstall,
stdout(data) {
console.log(data.toString('utf8'));
},
stderr(data) {
console.error(data.toString('utf8'));
},
onExit(code) {
console.log(`npm install exit code: ${code}`);
},
});
}

return app;
}
Expand Down
62 changes: 42 additions & 20 deletions packages/web/src/components/apps/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
CircleAlertIcon,
PanelBottomOpenIcon,
PanelBottomCloseIcon,
ExternalLinkIcon,
} from 'lucide-react';
import { Link } from 'react-router-dom';
import { SrcbookLogo } from '@/components/logos';
Expand Down Expand Up @@ -46,7 +47,7 @@ type PropsType = {

export default function EditorHeader(props: PropsType) {
const { app, updateApp } = useApp();
const { start: startPreview, stop: stopPreview, status: previewStatus } = usePreview();
const { url, start: startPreview, stop: stopPreview, status: previewStatus } = usePreview();
const { status: npmInstallStatus, nodeModulesExists } = usePackageJson();
const [isExporting, setIsExporting] = useState(false);
const { open, togglePane, panelIcon } = useLogs();
Expand Down Expand Up @@ -170,25 +171,46 @@ export default function EditorHeader(props: PropsType) {
</TooltipProvider>
) : null}
{props.tab === 'preview' && previewStatus !== 'stopped' ? (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="icon"
size="icon"
onClick={() => {
stopPreview();
props.onChangeTab('code');
}}
className="active:translate-y-0"
disabled={previewStatus === 'booting' || previewStatus === 'connecting'}
>
<StopCircleIcon size={18} />
</Button>
</TooltipTrigger>
<TooltipContent>Stop dev server</TooltipContent>
</Tooltip>
</TooltipProvider>
<>
{url && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="icon"
size="icon"
onClick={() => window.open(url as string, '_blank')}
className="active:translate-y-0"
disabled={previewStatus !== 'running'}
>
<ExternalLinkIcon size={18} />
</Button>
</TooltipTrigger>
<TooltipContent>Open in new tab</TooltipContent>
</Tooltip>
</TooltipProvider>
)}

<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="icon"
size="icon"
onClick={() => {
stopPreview();
props.onChangeTab('code');
}}
className="active:translate-y-0"
disabled={previewStatus !== 'running'}
>
<StopCircleIcon size={18} />
</Button>
</TooltipTrigger>
<TooltipContent>Stop dev server</TooltipContent>
</Tooltip>
</TooltipProvider>
</>
) : null}

<div
Expand Down

0 comments on commit 7f94377

Please sign in to comment.