phion
Version:
Phion Development Agent and Vite Plugin for seamless code sync and auto-deploy
230 lines (229 loc) • 8.56 kB
JavaScript
// src/plugin.ts
import { readFileSync, existsSync } from "fs";
import { resolve, join, dirname } from "path";
import { fileURLToPath } from "url";
var __filename = fileURLToPath(import.meta.url);
var __dirname = dirname(__filename);
var getPluginVersion = () => {
try {
const packageJsonPath = join(__dirname, "..", "package.json");
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
return packageJson.version;
} catch (error) {
console.warn("[Phion] Could not read version from package.json, using fallback");
return "0.0.1";
}
};
var PLUGIN_VERSION = getPluginVersion();
var DEFAULT_UPDATE_ENDPOINT = process.env.TOOLBAR_UPDATE_ENDPOINT || "http://localhost:3004/api/toolbar";
var findToolbarBundle = () => {
const __filename2 = fileURLToPath(import.meta.url);
const __dirname2 = dirname(__filename2);
const locations = [
// Direct in dist/toolbar folder (new package structure)
join(__dirname2, "toolbar", "index.global.js"),
// In node_modules
join(dirname(dirname(__dirname2)), "dist", "toolbar", "index.global.js"),
// One level up (when used as dependency)
join(dirname(dirname(dirname(__dirname2))), "phion", "dist", "toolbar", "index.global.js")
];
for (const location of locations) {
if (existsSync(location)) {
return location;
}
}
console.warn("[Phion] Could not find toolbar bundle at any of these locations:", locations);
return null;
};
function phionPlugin(options = {}) {
const {
configPath = "phion.config.json",
websocketUrl = "wss://api.phion.dev",
autoUpdate = true,
updateEndpoint = DEFAULT_UPDATE_ENDPOINT
} = options;
let config = null;
let toolbarEnabled = false;
let cachedToolbarCode = null;
let lastUpdateCheck = 0;
const UPDATE_CACHE_DURATION = 0;
async function checkForUpdates() {
if (!autoUpdate || Date.now() - lastUpdateCheck < UPDATE_CACHE_DURATION) {
return null;
}
try {
const channel = config?.toolbar?.updateChannel || "stable";
const response = await fetch(`${updateEndpoint}/check`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
currentVersion: PLUGIN_VERSION,
channel,
projectId: config?.projectId
})
});
if (response.ok) {
lastUpdateCheck = Date.now();
return await response.json();
}
} catch (error) {
if (config?.debug) {
console.warn("[phion-plugin] Update check failed:", error);
}
}
return null;
}
async function downloadToolbarUpdate(version) {
try {
const response = await fetch(version.url);
if (response.ok) {
const code = await response.text();
if (version.checksum) {
const actualChecksum = Buffer.from(code).toString("base64").slice(0, 8);
if (actualChecksum !== version.checksum.slice(0, 8)) {
console.warn("[phion-plugin] Checksum mismatch, using local version");
return null;
}
}
cachedToolbarCode = code;
if (config?.debug) {
console.log(`[phion-plugin] Updated to toolbar version ${version.version}`);
}
return code;
}
} catch (error) {
console.warn("[phion-plugin] Failed to download toolbar update:", error);
}
return null;
}
return {
name: "vite-plugin-phion",
configResolved(resolvedConfig) {
const configFilePath = resolve(resolvedConfig.root, configPath);
if (existsSync(configFilePath)) {
try {
const configContent = readFileSync(configFilePath, "utf-8");
config = JSON.parse(configContent);
toolbarEnabled = config?.toolbar?.enabled !== false && process.env.PHION_TOOLBAR !== "false";
} catch (error) {
console.warn("[phion-plugin] Failed to read config:", error);
}
}
},
configureServer(server) {
if (!toolbarEnabled || !config) return;
server.middlewares.use("/phion/config.js", (req, res, next) => {
try {
const toolbarConfig = {
projectId: config?.projectId || "",
websocketUrl: config?.wsUrl || websocketUrl,
// ✅ Всегда из конфига в первую очередь
position: config?.toolbar?.position || "top",
version: PLUGIN_VERSION,
autoUpdate: config?.toolbar?.autoUpdate !== false,
updateChannel: config?.toolbar?.updateChannel || "stable",
debug: config?.debug || false
};
res.writeHead(200, {
"Content-Type": "application/javascript",
"Cache-Control": "no-cache, no-store, must-revalidate, max-age=0",
Pragma: "no-cache",
Expires: "0",
ETag: `"${Date.now()}-${Math.random()}"`,
"Last-Modified": (/* @__PURE__ */ new Date()).toUTCString()
});
res.end(`window.PHION_CONFIG = ${JSON.stringify(toolbarConfig)};`);
} catch (err) {
console.error("[Phion] Error serving config:", err);
res.writeHead(500, { "Content-Type": "text/plain" });
res.end("Error generating toolbar config");
}
});
server.middlewares.use("/phion/toolbar.js", async (req, res, next) => {
try {
let toolbarCode = cachedToolbarCode;
try {
if (config?.toolbar?.autoUpdate !== false) {
const updateCheck = await checkForUpdates();
if (updateCheck?.hasUpdate && updateCheck.latestVersion) {
const updatedCode = await downloadToolbarUpdate(updateCheck.latestVersion);
if (updatedCode) {
toolbarCode = updatedCode;
}
}
}
} catch (fetchError) {
}
if (!toolbarCode) {
const toolbarPath = findToolbarBundle();
if (toolbarPath) {
toolbarCode = readFileSync(toolbarPath, "utf-8");
}
}
if (toolbarCode) {
res.writeHead(200, {
"Content-Type": "application/javascript",
"Cache-Control": "no-cache, no-store, must-revalidate, max-age=0",
Pragma: "no-cache",
Expires: "0",
ETag: `"${Date.now()}-${Math.random()}"`,
"Last-Modified": (/* @__PURE__ */ new Date()).toUTCString()
});
res.end(toolbarCode);
} else {
console.error("[Phion] Toolbar bundle not found");
res.writeHead(404, { "Content-Type": "text/plain" });
res.end("Toolbar bundle not found");
}
} catch (err) {
console.error("[Phion] Error serving toolbar:", err);
res.writeHead(500, { "Content-Type": "text/plain" });
res.end("Error loading toolbar");
}
});
server.middlewares.use("/phion/api/update-check", async (req, res) => {
if (req.method !== "POST") {
res.statusCode = 405;
res.end("Method not allowed");
return;
}
try {
const updateCheck = await checkForUpdates();
res.setHeader("Content-Type", "application/json");
res.end(
JSON.stringify(updateCheck || { hasUpdate: false, currentVersion: PLUGIN_VERSION })
);
} catch (error) {
res.statusCode = 500;
res.end(JSON.stringify({ error: "Update check failed" }));
}
});
},
transformIndexHtml(html) {
if (!toolbarEnabled || !config) return html;
const cacheBuster = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}-${process.pid}`;
const toolbarScript = `
<script>
// Clear any cached toolbar resources
if ('caches' in window) {
caches.keys().then(names => {
names.forEach(name => {
if (name.includes('phion') || name.includes('toolbar')) {
caches.delete(name);
}
});
});
}
</script>
<script src="/phion/config.js?v=${cacheBuster}&_cb=${Date.now()}&rand=${Math.random()}"></script>
<script src="/phion/toolbar.js?v=${cacheBuster}&_cb=${Date.now()}&rand=${Math.random()}"></script>
`;
return html.replace("</body>", `${toolbarScript}</body>`);
}
};
}
var plugin_default = phionPlugin;
export {
plugin_default as default,
phionPlugin
};