Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions apps/studio/src/client/components/studio-sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,17 @@ const data = {
export function StudioSidebar({
...props
}: React.ComponentProps<typeof Sidebar>) {
React.useEffect(() => {
// Installs the basic template to ensure it's available for new projects
void vanillaRpcClient.workspace.registry.template
.installDependencies({
templateName: "basic",
})
.catch((error: unknown) => {
logger.error("Error installing template", { error });
});
}, []);

const [userResult] = useAtom(userAtom);

const { data: favorites } = useQuery(
Expand Down
1 change: 1 addition & 0 deletions packages/workspace/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ export const APP_STATUSES = [
export const GIT_AUTHOR = { email: "agent@quests.dev", name: "Quests Agent" };
export const WEBSITE_URL = "https://quests.dev";
export const APP_NAME = "Quests";
export const INSTALL_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
2 changes: 1 addition & 1 deletion packages/workspace/src/logic/spawn-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
fromCallback,
} from "xstate";

import { INSTALL_TIMEOUT_MS } from "../constants";
import { type AppConfig } from "../lib/app-config/types";
import { cancelableTimeout, TimeoutError } from "../lib/cancelable-timeout";
import { pathExists } from "../lib/path-exists";
Expand All @@ -18,7 +19,6 @@ import { getWorkspaceServerURL } from "./server/url";

const BASE_RUNTIME_TIMEOUT_MS = 60 * 1000; // 1 minute
const RUNTIME_TIMEOUT_MULTIPLIER_MS = 30 * 1000; // 30 seconds
const INSTALL_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes

const portManager = new PortManager({
basePort: 9200,
Expand Down
87 changes: 87 additions & 0 deletions packages/workspace/src/rpc/routes/registry.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import fs from "node:fs/promises";
import { z } from "zod";

import {
DEFAULT_TEMPLATE_NAME,
INSTALL_TIMEOUT_MS,
REGISTRY_TEMPLATES_FOLDER,
} from "../../constants";
import { absolutePathJoin } from "../../lib/absolute-path-join";
import { templateExists } from "../../lib/app-dir-utils";
import { cancelableTimeout } from "../../lib/cancelable-timeout";
import {
getRegistryAppDetails,
RegistryAppDetailsSchema,
Expand Down Expand Up @@ -56,10 +64,89 @@ const screenshot = base
}
});

let HAS_INSTALLED_DEFAULT_TEMPLATE = false;

const installDependencies = base
.input(z.object({ templateName: z.literal(DEFAULT_TEMPLATE_NAME) }))
.handler(async ({ context, errors, input, signal }) => {
const { templateName } = input;

if (HAS_INSTALLED_DEFAULT_TEMPLATE) {
return {
message: `Dependencies already installed for template ${templateName}`,
success: true,
};
}

// Check if template exists
const exists = await templateExists({
folderName: templateName,
workspaceConfig: context.workspaceConfig,
});

if (!exists) {
throw errors.NOT_FOUND({
message: `Template ${templateName} not found`,
});
}

const templateDir = absolutePathJoin(
context.workspaceConfig.registryDir,
REGISTRY_TEMPLATES_FOLDER,
templateName,
);

try {
const installTimeout = cancelableTimeout(INSTALL_TIMEOUT_MS);
installTimeout.start();
const combinedSignal = signal
? AbortSignal.any([signal, installTimeout.controller.signal])
: installTimeout.controller.signal;

const result = await context.workspaceConfig.runShellCommand(
"pnpm install",
{
cwd: templateDir,
signal: combinedSignal,
},
);

installTimeout.cancel();

if (result.isErr()) {
// Log error but don't expose to UI
context.workspaceConfig.captureException(result.error);
return;
}

const processResult = await result.value;

if (processResult.exitCode !== 0) {
const error = new Error(
`pnpm install failed with exit code ${processResult.exitCode ?? "unknown"}: ${processResult.stderr}`,
);
context.workspaceConfig.captureException(error);
return;
}

HAS_INSTALLED_DEFAULT_TEMPLATE = true;
return;
} catch (error) {
// Log error but don't expose to UI
context.workspaceConfig.captureException(
error instanceof Error ? error : new Error(String(error)),
);
return;
}
});

export const registry = {
app: {
byFolderName,
list,
screenshot,
},
template: {
installDependencies,
},
};