Click to edit UI (#385)
- [x] add e2e test - happy case (make sure it clears selection and next prompt is empty, and preview is cleared); de-selection case - [x] shim - old & new file - [x] upgrade path - [x] add docs - [x] add try-catch to parser script - [x] make it work for next.js - [x] extract npm package - [x] make sure plugin doesn't apply in prod
This commit is contained in:
179
src/ipc/handlers/app_upgrade_handlers.ts
Normal file
179
src/ipc/handlers/app_upgrade_handlers.ts
Normal file
@@ -0,0 +1,179 @@
|
||||
import { createLoggedHandler } from "./safe_handle";
|
||||
import log from "electron-log";
|
||||
import { AppUpgrade } from "../ipc_types";
|
||||
import { db } from "../../db";
|
||||
import { apps } from "../../db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { getDyadAppPath } from "../../paths/paths";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { spawn } from "node:child_process";
|
||||
|
||||
const logger = log.scope("app_upgrade_handlers");
|
||||
const handle = createLoggedHandler(logger);
|
||||
|
||||
const availableUpgrades: Omit<AppUpgrade, "isNeeded">[] = [
|
||||
{
|
||||
id: "component-tagger",
|
||||
title: "Enable select component to edit",
|
||||
description:
|
||||
"Installs the Dyad component tagger Vite plugin and its dependencies.",
|
||||
manualUpgradeUrl: "https://dyad.sh/docs/upgrades/select-component",
|
||||
},
|
||||
];
|
||||
|
||||
async function getApp(appId: number) {
|
||||
const app = await db.query.apps.findFirst({
|
||||
where: eq(apps.id, appId),
|
||||
});
|
||||
if (!app) {
|
||||
throw new Error(`App with id ${appId} not found`);
|
||||
}
|
||||
return app;
|
||||
}
|
||||
|
||||
function isComponentTaggerUpgradeNeeded(appPath: string): boolean {
|
||||
const viteConfigPathJs = path.join(appPath, "vite.config.js");
|
||||
const viteConfigPathTs = path.join(appPath, "vite.config.ts");
|
||||
|
||||
let viteConfigPath;
|
||||
if (fs.existsSync(viteConfigPathTs)) {
|
||||
viteConfigPath = viteConfigPathTs;
|
||||
} else if (fs.existsSync(viteConfigPathJs)) {
|
||||
viteConfigPath = viteConfigPathJs;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const viteConfigContent = fs.readFileSync(viteConfigPath, "utf-8");
|
||||
return !viteConfigContent.includes("@dyad-sh/react-vite-component-tagger");
|
||||
} catch (e) {
|
||||
logger.error("Error reading vite config", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function applyComponentTagger(appPath: string) {
|
||||
const viteConfigPathJs = path.join(appPath, "vite.config.js");
|
||||
const viteConfigPathTs = path.join(appPath, "vite.config.ts");
|
||||
|
||||
let viteConfigPath;
|
||||
if (fs.existsSync(viteConfigPathTs)) {
|
||||
viteConfigPath = viteConfigPathTs;
|
||||
} else if (fs.existsSync(viteConfigPathJs)) {
|
||||
viteConfigPath = viteConfigPathJs;
|
||||
} else {
|
||||
throw new Error("Could not find vite.config.js or vite.config.ts");
|
||||
}
|
||||
|
||||
let content = await fs.promises.readFile(viteConfigPath, "utf-8");
|
||||
|
||||
// Add import statement if not present
|
||||
if (
|
||||
!content.includes(
|
||||
"import dyadComponentTagger from '@dyad-sh/react-vite-component-tagger';",
|
||||
)
|
||||
) {
|
||||
// Add it after the last import statement
|
||||
const lines = content.split("\n");
|
||||
let lastImportIndex = -1;
|
||||
for (let i = lines.length - 1; i >= 0; i--) {
|
||||
if (lines[i].startsWith("import ")) {
|
||||
lastImportIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
lines.splice(
|
||||
lastImportIndex + 1,
|
||||
0,
|
||||
"import dyadComponentTagger from '@dyad-sh/react-vite-component-tagger';",
|
||||
);
|
||||
content = lines.join("\n");
|
||||
}
|
||||
|
||||
// Add plugin to plugins array
|
||||
if (content.includes("plugins: [")) {
|
||||
if (!content.includes("dyadComponentTagger()")) {
|
||||
content = content.replace(
|
||||
"plugins: [",
|
||||
"plugins: [dyadComponentTagger(), ",
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
"Could not find `plugins: [` in vite.config.ts. Manual installation required.",
|
||||
);
|
||||
}
|
||||
|
||||
await fs.promises.writeFile(viteConfigPath, content);
|
||||
|
||||
// Install the dependency
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
logger.info("Installing component-tagger dependency");
|
||||
const process = spawn(
|
||||
"pnpm add -D @dyad-sh/react-vite-component-tagger || npm install --save-dev --legacy-peer-deps @dyad-sh/react-vite-component-tagger",
|
||||
{
|
||||
cwd: appPath,
|
||||
shell: true,
|
||||
stdio: "pipe",
|
||||
},
|
||||
);
|
||||
|
||||
process.stdout?.on("data", (data) => logger.info(data.toString()));
|
||||
process.stderr?.on("data", (data) => logger.error(data.toString()));
|
||||
|
||||
process.on("close", (code) => {
|
||||
if (code === 0) {
|
||||
logger.info("component-tagger dependency installed successfully");
|
||||
resolve();
|
||||
} else {
|
||||
logger.error(`Failed to install dependency, exit code ${code}`);
|
||||
reject(new Error("Failed to install dependency"));
|
||||
}
|
||||
});
|
||||
|
||||
process.on("error", (err) => {
|
||||
logger.error("Failed to spawn pnpm", err);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function registerAppUpgradeHandlers() {
|
||||
handle(
|
||||
"get-app-upgrades",
|
||||
async (_, { appId }: { appId: number }): Promise<AppUpgrade[]> => {
|
||||
const app = await getApp(appId);
|
||||
const appPath = getDyadAppPath(app.path);
|
||||
|
||||
const upgradesWithStatus = availableUpgrades.map((upgrade) => {
|
||||
let isNeeded = false;
|
||||
if (upgrade.id === "component-tagger") {
|
||||
isNeeded = isComponentTaggerUpgradeNeeded(appPath);
|
||||
}
|
||||
return { ...upgrade, isNeeded };
|
||||
});
|
||||
|
||||
return upgradesWithStatus;
|
||||
},
|
||||
);
|
||||
|
||||
handle(
|
||||
"execute-app-upgrade",
|
||||
async (_, { appId, upgradeId }: { appId: number; upgradeId: string }) => {
|
||||
if (!upgradeId) {
|
||||
throw new Error("upgradeId is required");
|
||||
}
|
||||
|
||||
const app = await getApp(appId);
|
||||
const appPath = getDyadAppPath(app.path);
|
||||
|
||||
if (upgradeId === "component-tagger") {
|
||||
await applyComponentTagger(appPath);
|
||||
} else {
|
||||
throw new Error(`Unknown upgrade id: ${upgradeId}`);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user