mirror of
https://github.com/stefanpejcic/openpanel
synced 2025-06-26 18:28:26 +00:00
packages
This commit is contained in:
4
packages/devtools-server/src/cli.ts
Normal file
4
packages/devtools-server/src/cli.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env node
|
||||
import { server } from "./index";
|
||||
|
||||
server().catch(() => 0);
|
||||
33
packages/devtools-server/src/client/index.html
Normal file
33
packages/devtools-server/src/client/index.html
Normal file
@@ -0,0 +1,33 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link
|
||||
rel="icon"
|
||||
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='40' height='40' fill='none' viewBox='0 0 40 40'%3E%3Cpath fill='%231D1E30' fill-rule='evenodd' d='M23.9975 12.3333c0 2.2092-1.7909 4-4 4-2.2091 0-4-1.7908-4-4 0-2.2091 1.7909-4 4-4 2.2091 0 4 1.7909 4 4Z' clip-rule='evenodd'/%3E%3Cpath fill='%231D1E30' fill-rule='evenodd' d='M13.7975 12.2c0-3.4242 2.7758-6.2 6.2-6.2 3.4242 0 6.2 2.7758 6.2 6.2v15.6c0 3.4242-2.7758 6.2-6.2 6.2-3.4242 0-6.2-2.7758-6.2-6.2V12.2Zm6.2-5.2c-2.8719 0-5.2 2.3281-5.2 5.2v15.6c0 2.8719 2.3281 5.2 5.2 5.2s5.2-2.3281 5.2-5.2V12.2c0-2.8719-2.3281-5.2-5.2-5.2Z' clip-rule='evenodd'/%3E%3Cpath fill='%231D1E30' fill-rule='evenodd' d='M15.5279 1.0557a9.9998 9.9998 0 0 1 8.9442 0l10 5C37.86 7.7497 40 11.2123 40 15v10c0 3.7877-2.14 7.2504-5.5279 8.9443l-10 5a10 10 0 0 1-8.9442 0l-10-5C2.14 32.2504 0 28.7877 0 25V15c0-3.7877 2.14-7.2504 5.5279-8.9443l10-5Zm.4472.8945a8.9999 8.9999 0 0 1 8.0498 0l10 5C37.074 8.4747 39 11.591 39 15v10c0 3.4089-1.926 6.5253-4.9751 8.0498l-10 5a8.9997 8.9997 0 0 1-8.0498 0l-10-5C2.926 31.5253 1 28.4089 1 25V15c0-3.4089 1.926-6.5253 4.975-8.0498l10.0001-5Z' clip-rule='evenodd'/%3E%3Cpath fill='%231D1E30' fill-rule='evenodd' d='M15.9751 1.9502a8.9999 8.9999 0 0 1 8.0498 0l10 5C37.074 8.4747 39 11.591 39 15v10c0 3.4089-1.926 6.5253-4.9751 8.0498l-10 5a8.9997 8.9997 0 0 1-8.0498 0l-10-5C2.926 31.5253 1 28.4089 1 25V15c0-3.4089 1.926-6.5253 4.975-8.0498l10.0001-5ZM19.9975 6c-3.4242 0-6.2 2.7758-6.2 6.2v15.6c0 3.4242 2.7758 6.2 6.2 6.2 3.4242 0 6.2-2.7758 6.2-6.2V12.2c0-3.4242-2.7758-6.2-6.2-6.2Z' clip-rule='evenodd'/%3E%3Cpath fill='url(%23paint0_linear_504_1891)' fill-rule='evenodd' d='M23.9975 12.3333c0 2.2092-1.7909 4-4 4-2.2091 0-4-1.7908-4-4 0-2.2091 1.7909-4 4-4 2.2091 0 4 1.7909 4 4Z' clip-rule='evenodd'/%3E%3Cpath fill='url(%23paint1_linear_504_1891)' fill-rule='evenodd' d='M13.7975 12.2c0-3.4242 2.7758-6.2 6.2-6.2 3.4242 0 6.2 2.7758 6.2 6.2v15.6c0 3.4242-2.7758 6.2-6.2 6.2-3.4242 0-6.2-2.7758-6.2-6.2V12.2Zm6.2-5.2c-2.8719 0-5.2 2.3281-5.2 5.2v15.6c0 2.8719 2.3281 5.2 5.2 5.2s5.2-2.3281 5.2-5.2V12.2c0-2.8719-2.3281-5.2-5.2-5.2Z' clip-rule='evenodd'/%3E%3Cpath fill='url(%23paint2_linear_504_1891)' fill-rule='evenodd' d='M15.5279 1.0557a9.9998 9.9998 0 0 1 8.9442 0l10 5C37.86 7.7497 40 11.2123 40 15v10c0 3.7877-2.14 7.2504-5.5279 8.9443l-10 5a10 10 0 0 1-8.9442 0l-10-5C2.14 32.2504 0 28.7877 0 25V15c0-3.7877 2.14-7.2504 5.5279-8.9443l10-5Zm.4472.8945a8.9999 8.9999 0 0 1 8.0498 0l10 5C37.074 8.4747 39 11.591 39 15v10c0 3.4089-1.926 6.5253-4.9751 8.0498l-10 5a8.9997 8.9997 0 0 1-8.0498 0l-10-5C2.926 31.5253 1 28.4089 1 25V15c0-3.4089 1.926-6.5253 4.975-8.0498l10.0001-5Z' clip-rule='evenodd'/%3E%3Cpath fill='url(%23paint3_radial_504_1891)' fill-rule='evenodd' d='M15.9751 1.9502a8.9999 8.9999 0 0 1 8.0498 0l10 5C37.074 8.4747 39 11.591 39 15v10c0 3.4089-1.926 6.5253-4.9751 8.0498l-10 5a8.9997 8.9997 0 0 1-8.0498 0l-10-5C2.926 31.5253 1 28.4089 1 25V15c0-3.4089 1.926-6.5253 4.975-8.0498l10.0001-5ZM19.9975 6c-3.4242 0-6.2 2.7758-6.2 6.2v15.6c0 3.4242 2.7758 6.2 6.2 6.2 3.4242 0 6.2-2.7758 6.2-6.2V12.2c0-3.4242-2.7758-6.2-6.2-6.2Z' clip-rule='evenodd'/%3E%3Cdefs%3E%3ClinearGradient id='paint0_linear_504_1891' x1='20' x2='20' y1='8.5' y2='16.5' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%2347EBEB'/%3E%3Cstop offset='1' stop-color='%2347EBEB' stop-opacity='.5'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint1_linear_504_1891' x1='20' x2='20' y1='6' y2='34' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%2347EBEB'/%3E%3Cstop offset='1' stop-color='%2347EBEB' stop-opacity='.5'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint2_linear_504_1891' x1='20' x2='20' y1='0' y2='40' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%2347EBEB'/%3E%3Cstop offset='.5' stop-color='%2347EBEB' stop-opacity='.5'/%3E%3Cstop offset='1' stop-color='%2347EBEB' stop-opacity='.5'/%3E%3C/linearGradient%3E%3CradialGradient id='paint3_radial_504_1891' cx='0' cy='0' r='1' gradientTransform='matrix(0 40 -40 0 20 0)' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%2347EBEB' stop-opacity='0'/%3E%3Cstop offset='.5' stop-color='%2347EBEB' stop-opacity='.25'/%3E%3Cstop offset='1' stop-color='%2347EBEB' stop-opacity='.5'/%3E%3C/radialGradient%3E%3C/defs%3E%3C/svg%3E"
|
||||
/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<!-- <link rel="manifest" href="/manifest.json" /> -->
|
||||
<title>Refine Devtools</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="./index.tsx"></script>
|
||||
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm run dev` or `yarn dev`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
--></body>
|
||||
</html>
|
||||
10
packages/devtools-server/src/client/index.tsx
Normal file
10
packages/devtools-server/src/client/index.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import { renderDevTools } from "@refinedev/devtools-ui";
|
||||
import "@refinedev/devtools-ui/style.css";
|
||||
|
||||
const container = document.getElementById("root");
|
||||
|
||||
if (container) {
|
||||
renderDevTools(container);
|
||||
} else {
|
||||
throw new Error("Could not find root element");
|
||||
}
|
||||
11
packages/devtools-server/src/client/vite.config.ts
Normal file
11
packages/devtools-server/src/client/vite.config.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
root: "src/client",
|
||||
build: {
|
||||
emptyOutDir: true,
|
||||
outDir: "../../dist/client",
|
||||
},
|
||||
});
|
||||
25
packages/devtools-server/src/constants.ts
Normal file
25
packages/devtools-server/src/constants.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import dotenv from "dotenv";
|
||||
|
||||
const refineEnv: Record<string, string> = {};
|
||||
dotenv.config({ processEnv: refineEnv });
|
||||
|
||||
const RAW_ENV_REFINE_DEVTOOLS_PORT =
|
||||
process.env.REFINE_DEVTOOLS_PORT || refineEnv.REFINE_DEVTOOLS_PORT;
|
||||
export const DEFAULT_SERVER_PORT = 5001;
|
||||
export const SERVER_PORT =
|
||||
Number(RAW_ENV_REFINE_DEVTOOLS_PORT) || DEFAULT_SERVER_PORT;
|
||||
|
||||
export const AUTH_SERVER_URL = __DEVELOPMENT__
|
||||
? "https://develop.auth.refine.dev"
|
||||
: "https://auth.refine.dev";
|
||||
|
||||
export const REFINE_API_URL = __DEVELOPMENT__
|
||||
? "https://develop.cloud.refine.dev"
|
||||
: "https://cloud2.refine.dev";
|
||||
|
||||
export const AUTH_TRIGGER_API_PATH = "/api/login";
|
||||
export const AUTH_CALLBACK_API_PATH = "/api/login-callback";
|
||||
export const AUTH_CALLBACK_UI_PATH = "/after-login";
|
||||
|
||||
export const FEED_MD_URL =
|
||||
"https://raw.githubusercontent.com/refinedev/refine/master/packages/devtools-server/FEED.md";
|
||||
34
packages/devtools-server/src/create-db.ts
Normal file
34
packages/devtools-server/src/create-db.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import type {
|
||||
DevtoolsEvent,
|
||||
DevtoolsEventPayloads,
|
||||
} from "@refinedev/devtools-shared";
|
||||
import type { WebSocket } from "ws";
|
||||
|
||||
export type Activity =
|
||||
DevtoolsEventPayloads[DevtoolsEvent.DEVTOOLS_ACTIVITY_UPDATE]["updatedActivities"][number];
|
||||
|
||||
export type Data = {
|
||||
connectedApp: null | string;
|
||||
clientWs: null | WebSocket;
|
||||
devtoolsWsClients: WebSocket[];
|
||||
appWsClients: WebSocket[];
|
||||
activities: Activity[];
|
||||
packages: string[];
|
||||
};
|
||||
|
||||
const defaultData: Data = {
|
||||
connectedApp: null,
|
||||
clientWs: null,
|
||||
// connections
|
||||
devtoolsWsClients: [],
|
||||
appWsClients: [],
|
||||
// data
|
||||
activities: [],
|
||||
packages: [],
|
||||
};
|
||||
|
||||
export const createDb = (): Data => {
|
||||
return {
|
||||
...defaultData,
|
||||
};
|
||||
};
|
||||
1
packages/devtools-server/src/define.d.ts
vendored
Normal file
1
packages/devtools-server/src/define.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
declare const __DEVELOPMENT__: boolean;
|
||||
65
packages/devtools-server/src/feed/get-feed.ts
Normal file
65
packages/devtools-server/src/feed/get-feed.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import fetch from "node-fetch";
|
||||
import matter from "gray-matter";
|
||||
import { marked } from "marked";
|
||||
import sanitizeHtml from "sanitize-html";
|
||||
|
||||
import type { Feed, FeedSection } from "@refinedev/devtools-shared";
|
||||
|
||||
import { FEED_MD_URL } from "src/constants";
|
||||
|
||||
const splitSections = (feed: string) => {
|
||||
const sections = feed.split("---section");
|
||||
|
||||
return sections.slice(1).map((section) => `---section${section}`);
|
||||
};
|
||||
|
||||
const contentToHtml = (content: string) => {
|
||||
const renderer = new marked.Renderer();
|
||||
renderer.link = function (...args) {
|
||||
const out = marked.Renderer.prototype.link.apply(this, args);
|
||||
return out.replace(/^<a/, '<a target="_blank" rel="noopener noreferrer"');
|
||||
};
|
||||
|
||||
const html = marked(content, {
|
||||
renderer,
|
||||
});
|
||||
|
||||
return sanitizeHtml(html, {
|
||||
allowedTags: sanitizeHtml.defaults.allowedTags.concat(["img"]),
|
||||
allowedAttributes: {
|
||||
...sanitizeHtml.defaults.allowedAttributes,
|
||||
img: ["src"],
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const parseSection = (section: string): FeedSection => {
|
||||
const parsed = matter(section.replace("---section", "---"));
|
||||
|
||||
return {
|
||||
...parsed.data,
|
||||
content: contentToHtml(parsed.content),
|
||||
} as FeedSection;
|
||||
};
|
||||
|
||||
const fetchFeed = async () => {
|
||||
try {
|
||||
const response = await fetch(FEED_MD_URL);
|
||||
|
||||
return response.text();
|
||||
} catch (_) {
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
export const getFeed = async (): Promise<Feed> => {
|
||||
try {
|
||||
const rawContent = await fetchFeed();
|
||||
const rawSections = splitSections(rawContent);
|
||||
const sections = rawSections.map(parseSection);
|
||||
|
||||
return sections;
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
165
packages/devtools-server/src/index.ts
Normal file
165
packages/devtools-server/src/index.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
import express from "express";
|
||||
|
||||
import { DevtoolsEvent, receive, send } from "@refinedev/devtools-shared";
|
||||
|
||||
import { serveClient } from "./serve-client";
|
||||
import { serveWs } from "./serve-ws";
|
||||
import { reloadOnChange } from "./reload-on-change";
|
||||
import { setupServer } from "./setup-server";
|
||||
import { type Activity, createDb } from "./create-db";
|
||||
import { serveApi } from "./serve-api";
|
||||
import { serveProxy } from "./serve-proxy";
|
||||
import { serveOpenInEditor } from "./serve-open-in-editor";
|
||||
|
||||
type Options = {
|
||||
projectPath?: string;
|
||||
onError?: () => void;
|
||||
};
|
||||
|
||||
export const server = async ({
|
||||
projectPath = process.cwd(),
|
||||
onError = () => {
|
||||
process.exit(1);
|
||||
},
|
||||
}: Options = {}) => {
|
||||
return new Promise((_, reject) => {
|
||||
const app = express();
|
||||
const server = setupServer(app, () => {
|
||||
reject();
|
||||
onError();
|
||||
});
|
||||
const ws = serveWs(server, () => {
|
||||
reject();
|
||||
onError();
|
||||
});
|
||||
|
||||
const db = createDb();
|
||||
|
||||
ws.on("connection", (client) => {
|
||||
// Initialize development client
|
||||
receive(client as any, DevtoolsEvent.DEVTOOLS_INIT, (data) => {
|
||||
if (db.connectedApp) {
|
||||
// send client the devtools client url if already connected
|
||||
send(client as any, DevtoolsEvent.DEVTOOLS_ALREADY_CONNECTED, {
|
||||
url: db.connectedApp,
|
||||
});
|
||||
} else {
|
||||
db.connectedApp = data.url;
|
||||
db.clientWs = client;
|
||||
|
||||
ws.clients.forEach((c) => {
|
||||
send(c as any, DevtoolsEvent.DEVTOOLS_CONNECTED_APP, {
|
||||
url: db.connectedApp,
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
receive(client as any, DevtoolsEvent.ACTIVITY, (data) => {
|
||||
// match by identifier, if identifier is same, update data instead of pushing
|
||||
const index = db.activities.findIndex(
|
||||
(activity) => activity.identifier === data.identifier,
|
||||
);
|
||||
|
||||
const record: Activity = {
|
||||
...data,
|
||||
createdAt: Date.now(),
|
||||
updatedAt: Date.now(),
|
||||
};
|
||||
|
||||
if (index > -1) {
|
||||
record.createdAt = db.activities[index].createdAt;
|
||||
|
||||
db.activities[index] = record;
|
||||
} else {
|
||||
db.activities.push(record);
|
||||
}
|
||||
|
||||
ws.clients.forEach((c) => {
|
||||
send(c as any, DevtoolsEvent.DEVTOOLS_ACTIVITY_UPDATE, {
|
||||
updatedActivities: [record],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
receive(
|
||||
client as any,
|
||||
DevtoolsEvent.DEVTOOLS_HIGHLIGHT_IN_MONITOR,
|
||||
({ name }) => {
|
||||
ws.clients.forEach((c) => {
|
||||
send(c as any, DevtoolsEvent.DEVTOOLS_HIGHLIGHT_IN_MONITOR_ACTION, {
|
||||
name,
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
receive(
|
||||
client as any,
|
||||
DevtoolsEvent.DEVTOOLS_INVALIDATE_QUERY,
|
||||
({ queryKey }) => {
|
||||
ws.clients.forEach((c) => {
|
||||
send(c as any, DevtoolsEvent.DEVTOOLS_INVALIDATE_QUERY_ACTION, {
|
||||
queryKey,
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
receive(client as any, DevtoolsEvent.DEVTOOLS_LOGIN_SUCCESS, () => {
|
||||
ws.clients.forEach((c) => {
|
||||
send(c as any, DevtoolsEvent.DEVTOOLS_RELOAD_AFTER_LOGIN, {});
|
||||
});
|
||||
});
|
||||
|
||||
receive(
|
||||
client as any,
|
||||
DevtoolsEvent.DEVTOOLS_LOGIN_FAILURE,
|
||||
({ error, code }) => {
|
||||
ws.clients.forEach((c) => {
|
||||
send(c as any, DevtoolsEvent.DEVTOOLS_DISPLAY_LOGIN_FAILURE, {
|
||||
error,
|
||||
code,
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// close connected app if client disconnects
|
||||
client.on("close", (_, reason) => {
|
||||
if (__DEVELOPMENT__) {
|
||||
console.log("Client disconnected", ws.clients.size);
|
||||
}
|
||||
|
||||
if (db.clientWs) {
|
||||
if (!ws.clients.has(db.clientWs)) {
|
||||
db.clientWs = null;
|
||||
db.connectedApp = null;
|
||||
|
||||
db.activities = [];
|
||||
|
||||
ws.clients.forEach((c) => {
|
||||
send(c as any, DevtoolsEvent.DEVTOOLS_DISCONNECTED_APP, {
|
||||
url: db.connectedApp,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (__DEVELOPMENT__) {
|
||||
console.log("Client connected", ws.clients.size);
|
||||
}
|
||||
});
|
||||
|
||||
reloadOnChange(ws);
|
||||
serveClient(app);
|
||||
serveApi(app, db);
|
||||
serveProxy(app);
|
||||
serveOpenInEditor(app, projectPath);
|
||||
|
||||
process.on("SIGTERM", () => {
|
||||
reject();
|
||||
});
|
||||
});
|
||||
};
|
||||
28
packages/devtools-server/src/packages/get-all-packages.ts
Normal file
28
packages/devtools-server/src/packages/get-all-packages.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import type { PackageType } from "@refinedev/devtools-shared";
|
||||
import { getInstalledPackageData } from "./get-installed-package-data";
|
||||
import { getPackagesFromPackageJSON } from "./get-packages-from-package-json";
|
||||
import { getChangelog } from "./get-changelog";
|
||||
import { getDocumentation } from "./get-documentation";
|
||||
|
||||
export const getAllPackages = async (projectPath?: string) => {
|
||||
try {
|
||||
const refinePackages = await getPackagesFromPackageJSON(projectPath);
|
||||
const installedVersions = await Promise.all(
|
||||
refinePackages.map(async (packageName) => {
|
||||
const currentInfo = await getInstalledPackageData(packageName);
|
||||
|
||||
return {
|
||||
name: packageName,
|
||||
currentVersion: currentInfo?.version,
|
||||
description: currentInfo?.description,
|
||||
changelog: getChangelog(packageName),
|
||||
documentation: getDocumentation(packageName),
|
||||
} as PackageType;
|
||||
}),
|
||||
);
|
||||
|
||||
return installedVersions;
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
514
packages/devtools-server/src/packages/get-available-packages.ts
Normal file
514
packages/devtools-server/src/packages/get-available-packages.ts
Normal file
@@ -0,0 +1,514 @@
|
||||
import type { AvailablePackageType } from "@refinedev/devtools-shared";
|
||||
import dedent from "dedent";
|
||||
import { getPackagesFromPackageJSON } from "./get-packages-from-package-json";
|
||||
|
||||
export const AVAILABLE_PACKAGES: AvailablePackageType[] = [
|
||||
{
|
||||
name: "@refinedev/ably",
|
||||
description: "Ably integration for Refine",
|
||||
install: "npm install @refinedev/ably",
|
||||
usage: dedent(
|
||||
`
|
||||
import { liveProvider, Ably } from "@refinedev/ably";
|
||||
|
||||
export const ablyClient = new Ably.Realtime("YOUR_API_TOKEN");
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<Refine
|
||||
liveProvider={liveProvider(ablyClient)}
|
||||
/* ... */
|
||||
>
|
||||
{/* ... */}
|
||||
</Refine>
|
||||
);
|
||||
};
|
||||
`.trim(),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "@refinedev/airtable",
|
||||
description: "Airtable integration for Refine",
|
||||
install: "npm install @refinedev/airtable",
|
||||
usage: dedent(
|
||||
`
|
||||
import dataProvider from "@refinedev/airtable";
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<Refine
|
||||
dataProvider={dataProvider("API_KEY", "BASE_ID")}
|
||||
/* ... */
|
||||
>
|
||||
{/* ... */}
|
||||
</Refine>
|
||||
);
|
||||
};
|
||||
`.trim(),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "@refinedev/antd",
|
||||
description: "Ant Design integration for Refine",
|
||||
install: "npm install @refinedev/antd antd",
|
||||
usage: dedent(
|
||||
`
|
||||
import { ThemedLayoutV2 } from "@refinedev/antd";
|
||||
|
||||
import "@refinedev/antd/dist/reset.css";
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<Refine
|
||||
/* ... */
|
||||
>
|
||||
<ThemedLayoutV2>
|
||||
{/* ... */}
|
||||
</ThemedLayoutV2>
|
||||
</Refine>
|
||||
);
|
||||
};
|
||||
`.trim(),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "@refinedev/appwrite",
|
||||
description: "Appwrite integration for Refine",
|
||||
install: "npm install @refinedev/appwrite",
|
||||
usage: dedent(
|
||||
`
|
||||
import { dataProvider, liveProvider, Account, Appwrite, Storage } from "@refinedev/appwrite";
|
||||
|
||||
const appwriteClient = new Appwrite();
|
||||
appwriteClient.setEndpoint("API_URL").setProject("PROJECT_ID");
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<Refine
|
||||
dataProvider={dataProvider(appwriteClient, { databaseId: "default" })}
|
||||
liveProvider={liveProvider(appwriteClient, { databaseId: "default" })}
|
||||
/* ... */
|
||||
>
|
||||
<ThemedLayout>
|
||||
{/* ... */}
|
||||
</ThemedLayout>
|
||||
</Refine>
|
||||
);
|
||||
};
|
||||
`.trim(),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "@refinedev/chakra-ui",
|
||||
description: "Chakra UI integration for Refine",
|
||||
install:
|
||||
"npm install @refinedev/chakra-ui @chakra-ui/react @emotion/react @emotion/styled framer-motion @tabler/icons-react",
|
||||
usage: dedent(
|
||||
`
|
||||
import { ThemedLayoutV2 } from "@refinedev/chakra-ui";
|
||||
import { ChakraProvider } from "@chakra-ui/react";
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<ChakraProvider>
|
||||
<Refine
|
||||
/* ... */
|
||||
>
|
||||
<ThemedLayoutV2>
|
||||
{/* ... */}
|
||||
</ThemedLayoutV2>
|
||||
</Refine>
|
||||
</ChakraProvider>
|
||||
);
|
||||
};
|
||||
`.trim(),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "@refinedev/graphql",
|
||||
description: "GraphQL integration for Refine",
|
||||
install: "npm install @refinedev/graphql",
|
||||
usage: dedent(
|
||||
`
|
||||
import dataProvider, { GraphQLClient } from "@refinedev/graphql";
|
||||
|
||||
const client = new GraphQLClient("YOUR_API_URL");
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<Refine
|
||||
dataProvider={dataProvider(client)}
|
||||
/* ... */
|
||||
>
|
||||
{/* ... */}
|
||||
</Refine>
|
||||
);
|
||||
};
|
||||
`.trim(),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "@refinedev/hasura",
|
||||
description: "GraphQL integration for Refine",
|
||||
install: "npm install @refinedev/hasura",
|
||||
usage: dedent(
|
||||
`
|
||||
import dataProvider, { GraphQLClient } from "@refinedev/hasura";
|
||||
|
||||
const client = new GraphQLClient("HASURA_API_URL", {
|
||||
headers: {
|
||||
"x-hasura-role": "public",
|
||||
},
|
||||
});
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<Refine
|
||||
dataProvider={dataProvider(client)}
|
||||
/* ... */
|
||||
>
|
||||
{/* ... */}
|
||||
</Refine>
|
||||
);
|
||||
};
|
||||
`.trim(),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "@refinedev/inferencer",
|
||||
description: "Auto generate views based on your APIs with Refine",
|
||||
install: "npm install @refinedev/inferencer",
|
||||
usage: dedent(
|
||||
`
|
||||
import { AntdInferencer } from "@refinedev/inferencer/antd";
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<Refine
|
||||
/* ... */
|
||||
>
|
||||
<AntdInferencer action="list" resource="posts" />
|
||||
</Refine>
|
||||
);
|
||||
};
|
||||
`.trim(),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "@refinedev/kbar",
|
||||
description: "Command palette integration with kbar for Refine",
|
||||
install: "npm install @refinedev/kbar",
|
||||
usage: dedent(
|
||||
`
|
||||
import { RefineKbar, RefineKbarProvider } from "@refinedev/kbar";
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<RefineKbarProvider>
|
||||
<Refine
|
||||
/* ... */
|
||||
>
|
||||
<RefineKbar />
|
||||
</Refine>
|
||||
</RefineKbarProvider>
|
||||
);
|
||||
};
|
||||
`.trim(),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "@refinedev/mantine",
|
||||
description: "Mantine UI integration for Refine",
|
||||
install:
|
||||
"npm install @refinedev/mantine @refinedev/react-table @mantine/core @mantine/hooks @mantine/form @mantine/notifications @emotion/react @tabler/icons-react",
|
||||
usage: dedent(
|
||||
`
|
||||
import { ThemedLayoutV2 } from "@refinedev/mantine";
|
||||
import { MantineProvider } from "@mantine/core";
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<MantineProvider>
|
||||
<Refine
|
||||
/* ... */
|
||||
>
|
||||
<ThemedLayoutV2>
|
||||
{/* ... */}
|
||||
</ThemedLayoutV2>
|
||||
</Refine>
|
||||
</MantineProvider>
|
||||
);
|
||||
};
|
||||
`.trim(),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "@refinedev/medusa",
|
||||
description: "Medusa store integration for Refine",
|
||||
install: "npm install @refinedev/medusa",
|
||||
usage: dedent(
|
||||
`
|
||||
import dataProvider, { authProvider } from "@refinedev/medusa";
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<Refine
|
||||
dataProvider={dataProvider("API_URL")}
|
||||
authProvider={authProvider("API_URL")}
|
||||
/* ... */
|
||||
>
|
||||
{/* ... */}
|
||||
</Refine>
|
||||
);
|
||||
};
|
||||
`.trim(),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "@refinedev/mui",
|
||||
description: "Material UI integration for Refine",
|
||||
install:
|
||||
"npm install @refinedev/mui @refinedev/react-hook-form @mui/material @mui/lab @mui/x-data-grid @emotion/react @emotion/styled react-hook-form",
|
||||
usage: dedent(
|
||||
`
|
||||
import { ThemedLayoutV2 } from "@refinedev/mui";
|
||||
|
||||
import CssBaseline from "@mui/material/CssBaseline";
|
||||
import GlobalStyles from "@mui/material/GlobalStyles";
|
||||
import { ThemeProvider } from "@mui/material/styles";
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<ThemeProvider>
|
||||
<CssBaseline />
|
||||
<GlobalStyles styles={{ html: { WebkitFontSmoothing: "auto" } }} />
|
||||
<Refine
|
||||
/* ... */
|
||||
>
|
||||
<ThemedLayoutV2>
|
||||
{/* ... */}
|
||||
</ThemedLayoutV2>
|
||||
</Refine>
|
||||
</ThemeProvider>
|
||||
);
|
||||
};
|
||||
`.trim(),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "@refinedev/nestjs-query",
|
||||
description: "NestJS Query data provider integration for Refine",
|
||||
install: "npm install @refinedev/nestjs-query graphql-ws",
|
||||
usage: dedent(
|
||||
`
|
||||
import dataProvider, {
|
||||
GraphQLClient,
|
||||
liveProvider,
|
||||
} from "@refinedev/nestjs-query";
|
||||
|
||||
import { createClient } from "graphql-ws";
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<Refine
|
||||
dataProvider={dataProvider(new GraphQLClient( "API_URL" ))}
|
||||
liveProvider={liveProvider(createClient({ url: "WS_URL" }))}
|
||||
/* ... */
|
||||
>
|
||||
{/* ... */}
|
||||
</Refine>
|
||||
);
|
||||
};
|
||||
`.trim(),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "@refinedev/nestjsx-crud",
|
||||
description: "NestJSX CRUD data provider integration for Refine",
|
||||
install: "npm install @refinedev/nestjsx-crud",
|
||||
usage: dedent(
|
||||
`
|
||||
import dataProvider from "@refinedev/nestjsx-crud";
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<Refine
|
||||
dataProvider={dataProvider("API_URL")}
|
||||
/* ... */
|
||||
>
|
||||
{/* ... */}
|
||||
</Refine>
|
||||
);
|
||||
};
|
||||
`.trim(),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "@refinedev/react-hook-form",
|
||||
description: "React Hook Form integration for Refine",
|
||||
install: "npm install @refinedev/react-hook-form react-hook-form",
|
||||
usage: dedent(
|
||||
`
|
||||
import { useForm } from "@refinedev/react-hook-form";
|
||||
|
||||
const EditPost = () => {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState,
|
||||
refineCore,
|
||||
} = useForm({
|
||||
refineCoreProps: {
|
||||
resource: "posts",
|
||||
id: "1",
|
||||
},
|
||||
});
|
||||
|
||||
return /* ... */
|
||||
};
|
||||
`.trim(),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "@refinedev/react-table",
|
||||
description: "Tanstack React Table integration for Refine",
|
||||
install: "npm install @refinedev/react-table @tanstack/react-table",
|
||||
usage: dedent(
|
||||
`
|
||||
import { useTable } from "@refinedev/react-table";
|
||||
|
||||
import { ColumnDef, flexRender } from "@tanstack/react-table";
|
||||
|
||||
const EditPost = () => {
|
||||
const columns = React.useMemo<ColumnDef<IPost>[]>(
|
||||
() => [
|
||||
{
|
||||
id: "id",
|
||||
header: "ID",
|
||||
accessorKey: "id",
|
||||
},
|
||||
{
|
||||
id: "title",
|
||||
header: "Title",
|
||||
accessorKey: "title",
|
||||
meta: {
|
||||
filterOperator: "contains",
|
||||
},
|
||||
},
|
||||
], []);
|
||||
|
||||
const tableInstance = useTable({
|
||||
columns,
|
||||
refineCoreProps: {
|
||||
resource: "posts",
|
||||
}
|
||||
});
|
||||
|
||||
return /* ... */
|
||||
};
|
||||
`.trim(),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "@refinedev/simple-rest",
|
||||
description: "Data provider integration for REST APIs with Refine",
|
||||
install: "npm install @refinedev/simple-rest",
|
||||
usage: dedent(
|
||||
`
|
||||
import dataProvider from "@refinedev/simple-rest";
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<Refine
|
||||
dataProvider={dataProvider("API_URL")}
|
||||
/* ... */
|
||||
>
|
||||
{/* ... */}
|
||||
</Refine>
|
||||
);
|
||||
};
|
||||
`.trim(),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "@refinedev/supabase",
|
||||
description:
|
||||
"Data provider and live provider integrations for Supabase with Refine",
|
||||
install: "npm install @refinedev/supabase",
|
||||
usage: dedent(
|
||||
`
|
||||
import { dataProvider, liveProvider, createClient } from "@refinedev/supabase";
|
||||
|
||||
const supabaseClient = createClient("SUPABASE_URL", "SUPABASE_KEY");
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<Refine
|
||||
dataProvider={dataProvider(supabaseClient)}
|
||||
liveProvider={liveProvider(supabaseClient)}
|
||||
/* ... */
|
||||
>
|
||||
{/* ... */}
|
||||
</Refine>
|
||||
);
|
||||
};
|
||||
`.trim(),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "@refinedev/strapi",
|
||||
description: "Strapi integration of Refine",
|
||||
install: "npm install @refinedev/strapi axios",
|
||||
usage: dedent(
|
||||
`
|
||||
import { DataProvider, AuthHelper } from "@refinedev/strapi";
|
||||
|
||||
const axiosInstance = axios.create();
|
||||
const strapiAuthHelper = AuthHelper("API_URL");
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<Refine
|
||||
dataProvider={DataProvider("API_URL", axiosInstance)}
|
||||
/* ... */
|
||||
>
|
||||
{/* ... */}
|
||||
</Refine>
|
||||
);
|
||||
};
|
||||
`.trim(),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "@refinedev/strapi-v4",
|
||||
description: "StrapiV4 integration of Refine",
|
||||
install: "npm install @refinedev/strapi-v4 axios",
|
||||
usage: dedent(
|
||||
`
|
||||
import { DataProvider, AuthHelper } from "@refinedev/strapi-v4";
|
||||
|
||||
const axiosInstance = axios.create();
|
||||
const strapiAuthHelper = AuthHelper("API_URL");
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<Refine
|
||||
dataProvider={DataProvider("API_URL", axiosInstance)}
|
||||
/* ... */
|
||||
>
|
||||
{/* ... */}
|
||||
</Refine>
|
||||
);
|
||||
};
|
||||
`.trim(),
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
export const getAvailablePackages = async (projectPath?: string) => {
|
||||
const installedRefinePackages = await getPackagesFromPackageJSON(projectPath);
|
||||
|
||||
return AVAILABLE_PACKAGES.filter(
|
||||
(p) => !installedRefinePackages.includes(p.name),
|
||||
);
|
||||
};
|
||||
3
packages/devtools-server/src/packages/get-changelog.ts
Normal file
3
packages/devtools-server/src/packages/get-changelog.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export const getChangelog = (packageName: string) => {
|
||||
return packageName.replace("@refinedev/", "https://c.refine.dev/");
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
export const getDocumentation = (packageName: string) => {
|
||||
return "https://refine.dev/docs/api-reference/general-concepts/";
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
import fs from "fs";
|
||||
import { getInstalledPackageJSONPath } from "./get-installed-package-json-path";
|
||||
|
||||
export const getInstalledPackageData = async (packageName: string) => {
|
||||
try {
|
||||
const packagePath = await getInstalledPackageJSONPath(packageName);
|
||||
|
||||
if (!packagePath) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const parsed = JSON.parse(
|
||||
fs.readFileSync(packagePath, { encoding: "utf-8" }),
|
||||
);
|
||||
|
||||
return {
|
||||
name: parsed.name,
|
||||
version: parsed.version,
|
||||
description: parsed.description,
|
||||
};
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
import path from "path";
|
||||
import globby from "globby";
|
||||
|
||||
export const getInstalledPackageJSONPath = async (packageName: string) => {
|
||||
try {
|
||||
const filesFromGlobbySearch = await globby(
|
||||
`node_modules/${packageName}/package.json`,
|
||||
{
|
||||
onlyFiles: true,
|
||||
},
|
||||
);
|
||||
|
||||
let fileFromModule: string | null = null;
|
||||
|
||||
try {
|
||||
const pkgJsonPath = require.resolve(
|
||||
path.join(packageName, "package.json"),
|
||||
);
|
||||
if (pkgJsonPath) {
|
||||
fileFromModule = pkgJsonPath;
|
||||
}
|
||||
} catch (err) {
|
||||
//
|
||||
}
|
||||
|
||||
return (
|
||||
[
|
||||
...filesFromGlobbySearch,
|
||||
...(fileFromModule ? [fileFromModule] : []),
|
||||
][0] ?? null
|
||||
);
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
import execa from "execa";
|
||||
|
||||
export const getLatestPackageData = async (
|
||||
packageName: string,
|
||||
): Promise<{ name: string; version?: string }> => {
|
||||
try {
|
||||
const { stdout } = await execa("npm", [
|
||||
"view",
|
||||
packageName,
|
||||
"name",
|
||||
"version",
|
||||
"--json",
|
||||
]);
|
||||
const parsed = JSON.parse(stdout);
|
||||
|
||||
return parsed;
|
||||
} catch (e) {
|
||||
return { name: packageName };
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
import path from "path";
|
||||
import { readJSON } from "fs-extra";
|
||||
|
||||
export const getPackagesFromPackageJSON = async (
|
||||
projectPath: string = process.cwd(),
|
||||
) => {
|
||||
const packageJson = await readJSON(path.join(projectPath, "package.json"), {
|
||||
encoding: "utf-8",
|
||||
});
|
||||
|
||||
const refinePackages = Object.keys(packageJson.dependencies).filter(
|
||||
(packageName) => packageName.startsWith("@refinedev/"),
|
||||
);
|
||||
|
||||
return refinePackages;
|
||||
};
|
||||
21
packages/devtools-server/src/packages/update-package.ts
Normal file
21
packages/devtools-server/src/packages/update-package.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { detect } from "package-manager-detector";
|
||||
import execa from "execa";
|
||||
|
||||
export const updatePackage = async (
|
||||
packages: string[],
|
||||
projectPath: string = process.cwd(),
|
||||
) => {
|
||||
try {
|
||||
const detected = await detect({ cwd: projectPath });
|
||||
const [pm] = (detected?.agent || "npm").split("@");
|
||||
|
||||
const { failed } = await execa(pm, [
|
||||
"install",
|
||||
...packages.map((p) => `${p}@latest`),
|
||||
]);
|
||||
|
||||
return !failed;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
import { readJSON } from "fs-extra";
|
||||
import path from "path";
|
||||
|
||||
export const getProjectIdFromPackageJson = async (
|
||||
projectPath = process.cwd(),
|
||||
) => {
|
||||
try {
|
||||
const packageJson = await readJSON(path.join(projectPath, "package.json"), {
|
||||
encoding: "utf-8",
|
||||
});
|
||||
|
||||
const projectId = packageJson?.refine?.projectId as string;
|
||||
|
||||
if (projectId) {
|
||||
return projectId;
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
import execa from "execa";
|
||||
|
||||
export const setProjectIdToPackageJson = async (
|
||||
projectId: string,
|
||||
projectPath = process.cwd(),
|
||||
) => {
|
||||
try {
|
||||
execa.sync("npm", ["pkg", "set", `refine.projectId=${projectId}`], {
|
||||
cwd: projectPath,
|
||||
});
|
||||
return true;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
import execa from "execa";
|
||||
import path from "path";
|
||||
|
||||
export const setProjectIdToRefineComponent = async (
|
||||
projectId: string,
|
||||
projectPath = process.cwd(),
|
||||
) => {
|
||||
try {
|
||||
const jscodeshiftExecutable = require.resolve(".bin/jscodeshift");
|
||||
|
||||
const execution = execa.sync(
|
||||
jscodeshiftExecutable,
|
||||
[
|
||||
"./",
|
||||
"--extensions=ts,tsx,js,jsx",
|
||||
"--parser=tsx",
|
||||
`--transform=${path.resolve(
|
||||
path.join(__dirname, "..", "src", "project-id", "transform.ts"),
|
||||
)}`,
|
||||
"--ignore-pattern=**/.cache/**",
|
||||
"--ignore-pattern=**/node_modules/**",
|
||||
"--ignore-pattern=**/build/**",
|
||||
"--ignore-pattern=**/dist/**",
|
||||
"--ignore-pattern=**/.next/**",
|
||||
`--__projectId=${projectId}`,
|
||||
],
|
||||
{
|
||||
cwd: projectPath,
|
||||
timeout: 1000 * 10,
|
||||
},
|
||||
);
|
||||
|
||||
if (execution.stderr) {
|
||||
console.error(execution.stderr);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
return;
|
||||
};
|
||||
88
packages/devtools-server/src/project-id/transform.ts
Normal file
88
packages/devtools-server/src/project-id/transform.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import type { namedTypes } from "ast-types";
|
||||
import type {
|
||||
API,
|
||||
ASTPath,
|
||||
Collection,
|
||||
FileInfo,
|
||||
JSCodeshift,
|
||||
Options,
|
||||
} from "jscodeshift";
|
||||
|
||||
export const parser = "tsx";
|
||||
|
||||
const transformRefineOptions = (
|
||||
j: JSCodeshift,
|
||||
root: Collection<any>,
|
||||
projectId: string,
|
||||
) => {
|
||||
const refineElements: Array<ASTPath<namedTypes.JSXElement>> = [];
|
||||
|
||||
root.findJSXElements("Refine").forEach((path) => {
|
||||
refineElements.push(path);
|
||||
});
|
||||
|
||||
for (const path of refineElements) {
|
||||
const props = path.node.openingElement.attributes;
|
||||
|
||||
const optionsProp: any = props?.find(
|
||||
(attribute) =>
|
||||
attribute.type === "JSXAttribute" && attribute.name.name === "options",
|
||||
);
|
||||
if (!optionsProp) {
|
||||
path.node.openingElement.attributes?.push(
|
||||
j.jsxAttribute(
|
||||
j.jsxIdentifier("options"),
|
||||
j.jsxExpressionContainer(
|
||||
j.objectExpression([
|
||||
j.objectProperty(
|
||||
j.identifier("projectId"),
|
||||
j.stringLiteral(projectId),
|
||||
),
|
||||
]),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// for options={optionsProp}
|
||||
if (!optionsProp?.value.expression.properties) {
|
||||
break;
|
||||
}
|
||||
|
||||
// for options has already projectId
|
||||
const hasProjectId = optionsProp?.value.expression.properties.find(
|
||||
(p: any) => {
|
||||
if (p.type === "ObjectProperty") {
|
||||
return p.key.name === "projectId";
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
);
|
||||
|
||||
if (hasProjectId) break;
|
||||
|
||||
optionsProp?.value.expression.properties.push(
|
||||
j.objectProperty(j.identifier("projectId"), j.stringLiteral(projectId)),
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return root;
|
||||
};
|
||||
|
||||
export default function transformer(
|
||||
file: FileInfo,
|
||||
api: API,
|
||||
options: Options,
|
||||
) {
|
||||
const j = api.jscodeshift;
|
||||
const source = j(file.source);
|
||||
|
||||
transformRefineOptions(j, source, options.__projectId);
|
||||
|
||||
return source.toSource();
|
||||
}
|
||||
18
packages/devtools-server/src/project-id/update-project-id.ts
Normal file
18
packages/devtools-server/src/project-id/update-project-id.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { setProjectIdToPackageJson } from "./set-project-id-to-package-json";
|
||||
import { setProjectIdToRefineComponent } from "./set-project-id-to-refine-component";
|
||||
|
||||
export const updateProjectId = async (
|
||||
projectId: string,
|
||||
projectPath = process.cwd(),
|
||||
) => {
|
||||
try {
|
||||
await Promise.all([
|
||||
setProjectIdToPackageJson(projectId, projectPath),
|
||||
setProjectIdToRefineComponent(projectId, projectPath),
|
||||
]);
|
||||
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
32
packages/devtools-server/src/reload-on-change.ts
Normal file
32
packages/devtools-server/src/reload-on-change.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import debounce from "lodash/debounce";
|
||||
import { DevtoolsEvent, send } from "@refinedev/devtools-shared";
|
||||
|
||||
import type { Server } from "ws";
|
||||
import { OPEN } from "ws";
|
||||
|
||||
export const reloadOnChange = __DEVELOPMENT__
|
||||
? (ws: Server) => {
|
||||
const reloadEmitter = debounce(() => {
|
||||
setTimeout(() => {
|
||||
ws.clients.forEach((client) => {
|
||||
if (client.readyState === OPEN) {
|
||||
console.log("Reloading connected client...");
|
||||
send(client as any, DevtoolsEvent.RELOAD, {});
|
||||
}
|
||||
});
|
||||
}, 800);
|
||||
}, 1000);
|
||||
|
||||
const watcher = fs.watch(
|
||||
path.resolve(__dirname, "client"),
|
||||
{ recursive: true },
|
||||
reloadEmitter,
|
||||
);
|
||||
|
||||
process.on("SIGTERM", () => {
|
||||
watcher.close();
|
||||
});
|
||||
}
|
||||
: () => 0;
|
||||
174
packages/devtools-server/src/serve-api.ts
Normal file
174
packages/devtools-server/src/serve-api.ts
Normal file
@@ -0,0 +1,174 @@
|
||||
import type { Express } from "express";
|
||||
import { json } from "express";
|
||||
import uniq from "lodash/uniq";
|
||||
import type {
|
||||
AvailablePackageType,
|
||||
Feed,
|
||||
PackageType,
|
||||
} from "@refinedev/devtools-shared";
|
||||
|
||||
import type { Data } from "./create-db";
|
||||
import { getFeed } from "./feed/get-feed";
|
||||
import { getAllPackages } from "./packages/get-all-packages";
|
||||
import { getAvailablePackages } from "./packages/get-available-packages";
|
||||
import { updatePackage } from "./packages/update-package";
|
||||
import { getLatestPackageData } from "./packages/get-latest-package-data";
|
||||
import { getProjectIdFromPackageJson } from "./project-id/get-project-id-from-package-json";
|
||||
import { updateProjectId } from "./project-id/update-project-id";
|
||||
|
||||
export const serveApi = (app: Express, db: Data) => {
|
||||
app.use("/api", json());
|
||||
|
||||
app.get("/api/connected-app", (_, res) => {
|
||||
res.json({ url: db.connectedApp });
|
||||
});
|
||||
|
||||
app.get("/api/activities", (req, res) => {
|
||||
const { offset = 0, limit = db.activities.length } = req.query;
|
||||
|
||||
res.setHeader("x-total-count", db.activities.length);
|
||||
|
||||
res.json({
|
||||
data: db.activities.slice(Number(offset), Number(limit)),
|
||||
});
|
||||
});
|
||||
|
||||
app.get("/api/activities/reset", (_, res) => {
|
||||
db.activities = [];
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
app.get("/api/unique-trace-items", (req, res) => {
|
||||
res.header("Access-Control-Allow-Origin", "*");
|
||||
res.header(
|
||||
"Access-Control-Allow-Headers",
|
||||
"Origin, X-Requested-With, Content-Type, Accept",
|
||||
);
|
||||
|
||||
const traceItems = db.activities.flatMap(
|
||||
(activity) =>
|
||||
activity.trace?.map((t) => t.function).filter(Boolean) ?? [],
|
||||
) as string[];
|
||||
const uniqueTraceItems = uniq(traceItems);
|
||||
|
||||
res.setHeader("x-total-count", uniqueTraceItems.length);
|
||||
|
||||
res.json({ data: uniqueTraceItems });
|
||||
});
|
||||
|
||||
let cachedInstalledPackages: PackageType[] | null = null;
|
||||
app.get("/api/installed-packages", async (req, res) => {
|
||||
const { force } = req.query ?? {};
|
||||
|
||||
if (!cachedInstalledPackages || force) {
|
||||
cachedInstalledPackages = await getAllPackages();
|
||||
}
|
||||
|
||||
res.header("x-total-count", `${cachedInstalledPackages.length}`);
|
||||
|
||||
res.json({ data: cachedInstalledPackages });
|
||||
});
|
||||
|
||||
let cachedAvailablePackages: AvailablePackageType[] | null = null;
|
||||
app.get("/api/available-packages", async (_, res) => {
|
||||
if (!cachedAvailablePackages) {
|
||||
cachedAvailablePackages = await getAvailablePackages();
|
||||
}
|
||||
|
||||
res.header("x-total-count", `${cachedAvailablePackages.length}`);
|
||||
|
||||
res.json({ data: cachedAvailablePackages });
|
||||
});
|
||||
|
||||
const cachedLatestPackages = new Map<string, any>();
|
||||
app.get("/api/packages/:packageName/latest", async (req, res) => {
|
||||
const { packageName } = req.params ?? {};
|
||||
|
||||
if (!packageName) {
|
||||
res.status(400).json({ error: "Package name is required" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cachedLatestPackages.has(packageName)) {
|
||||
const latest = await getLatestPackageData(packageName);
|
||||
cachedLatestPackages.set(packageName, latest);
|
||||
}
|
||||
|
||||
return res.json({ data: cachedLatestPackages.get(packageName) });
|
||||
});
|
||||
|
||||
app.post("/api/packages/install", async (req, res) => {
|
||||
const { packages } = req.body ?? {};
|
||||
|
||||
if (packages?.length === 0) {
|
||||
res.status(400).json({ error: "Package name is required" });
|
||||
return;
|
||||
}
|
||||
|
||||
const success = await updatePackage(packages as string[]);
|
||||
|
||||
if (success) {
|
||||
cachedInstalledPackages = null;
|
||||
cachedAvailablePackages = null;
|
||||
res.status(200).json({ success: true });
|
||||
} else {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: "Failed to update package",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let cachedFeed: Feed | null = null;
|
||||
app.get("/api/feed", async (req, res) => {
|
||||
if (!cachedFeed) {
|
||||
cachedFeed = await getFeed();
|
||||
}
|
||||
|
||||
res.header("x-total-count", `${cachedFeed.length}`);
|
||||
|
||||
res.json({ data: cachedFeed });
|
||||
});
|
||||
|
||||
app.get("/api/project-id/status", async (_, res) => {
|
||||
const CODES = {
|
||||
OK: 0,
|
||||
NOT_FOUND: 1,
|
||||
ERROR: 2,
|
||||
};
|
||||
|
||||
const projectId = await getProjectIdFromPackageJson();
|
||||
|
||||
if (projectId) {
|
||||
res.status(200).json({ projectId, status: CODES.OK });
|
||||
return;
|
||||
}
|
||||
if (projectId === false) {
|
||||
res.status(200).json({ projectId: null, status: CODES.NOT_FOUND });
|
||||
return;
|
||||
}
|
||||
res.status(200).json({ projectId: null, status: CODES.ERROR });
|
||||
return;
|
||||
});
|
||||
|
||||
app.post("/api/project-id/update", async (req, res) => {
|
||||
const { projectId } = req.body ?? {};
|
||||
|
||||
if (!projectId) {
|
||||
res.status(400).json({ error: "Project ID is required" });
|
||||
return;
|
||||
}
|
||||
|
||||
const success = await updateProjectId(projectId);
|
||||
|
||||
if (success) {
|
||||
res.status(200).json({ success: true });
|
||||
return;
|
||||
}
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: "Failed to update project ID",
|
||||
});
|
||||
return;
|
||||
});
|
||||
};
|
||||
18
packages/devtools-server/src/serve-client.ts
Normal file
18
packages/devtools-server/src/serve-client.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import express from "express";
|
||||
import path from "path";
|
||||
|
||||
import type { Express } from "express";
|
||||
|
||||
export const serveClient = (app: Express) => {
|
||||
app.use(express.static(path.join(__dirname, "client")));
|
||||
|
||||
app.use((req, res, next) => {
|
||||
if (req.path.startsWith("/api")) {
|
||||
return next();
|
||||
}
|
||||
if (req.path.startsWith("/open-in-editor")) {
|
||||
return next();
|
||||
}
|
||||
res.status(200).sendFile(path.join(`${__dirname}/client/index.html`));
|
||||
});
|
||||
};
|
||||
16
packages/devtools-server/src/serve-open-in-editor.ts
Normal file
16
packages/devtools-server/src/serve-open-in-editor.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import type { Express } from "express";
|
||||
import path from "path";
|
||||
|
||||
export const serveOpenInEditor = (app: Express, basePath: string) => {
|
||||
app.get("/open-in-editor/*", (req, res) => {
|
||||
const { line, column } = req.query;
|
||||
|
||||
const filePath = req.path.replace("/open-in-editor", "");
|
||||
|
||||
const vscodeUrl = `vscode://file/${path.join(basePath, filePath)}?${
|
||||
line ? `line=${line}` : ""
|
||||
}${column ? `&column=${column}` : ""}`;
|
||||
|
||||
res.redirect(vscodeUrl);
|
||||
});
|
||||
};
|
||||
151
packages/devtools-server/src/serve-proxy.ts
Normal file
151
packages/devtools-server/src/serve-proxy.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
import path from "path";
|
||||
import { readJSON, writeJSON } from "fs-extra";
|
||||
import { createProxyMiddleware, fixRequestBody } from "http-proxy-middleware";
|
||||
import {
|
||||
REFINE_API_URL,
|
||||
AUTH_SERVER_URL,
|
||||
AUTH_CALLBACK_API_PATH,
|
||||
AUTH_CALLBACK_UI_PATH,
|
||||
AUTH_TRIGGER_API_PATH,
|
||||
} from "./constants";
|
||||
import { getProjectIdFromPackageJson } from "./project-id/get-project-id-from-package-json";
|
||||
|
||||
import type { Express, RequestHandler } from "express";
|
||||
|
||||
const persistPath = path.join(__dirname, "..", ".persist.json");
|
||||
|
||||
const saveAuth = async (token?: string, jwt?: string) => {
|
||||
try {
|
||||
await writeJSON(persistPath, { token, jwt });
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
};
|
||||
|
||||
const loadAuth = async () => {
|
||||
try {
|
||||
return (await readJSON(persistPath)) as { token?: string; jwt?: string };
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
export const serveProxy = async (app: Express) => {
|
||||
let { token, jwt } = await loadAuth();
|
||||
|
||||
const authProxy = createProxyMiddleware({
|
||||
target: `${AUTH_SERVER_URL}/api/.auth`,
|
||||
secure: false,
|
||||
changeOrigin: true,
|
||||
logger: __DEVELOPMENT__ ? console : undefined,
|
||||
on: {
|
||||
proxyReq: fixRequestBody,
|
||||
proxyRes: (_proxyRes, req) => {
|
||||
if (req.url?.includes("self-service/logout/api")) {
|
||||
token = undefined;
|
||||
jwt = undefined;
|
||||
saveAuth();
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const refineProxy = createProxyMiddleware({
|
||||
target: `${REFINE_API_URL}/.refine`,
|
||||
secure: false,
|
||||
changeOrigin: true,
|
||||
logger: __DEVELOPMENT__ ? console : undefined,
|
||||
on: {
|
||||
proxyReq: fixRequestBody,
|
||||
},
|
||||
});
|
||||
|
||||
let currentProjectId: string | null | false = null;
|
||||
const projectIdAppender: RequestHandler = async (req, _res, next) => {
|
||||
if (!currentProjectId) {
|
||||
currentProjectId = await getProjectIdFromPackageJson();
|
||||
}
|
||||
|
||||
if (currentProjectId) {
|
||||
req.headers["x-project-id"] = currentProjectId;
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
const appendAuth: RequestHandler = async (req, _res, next) => {
|
||||
if (token) {
|
||||
req.headers["X-Session-Token"] = token;
|
||||
}
|
||||
if (req.url?.includes("self-service/logout/api")) {
|
||||
req.body = {
|
||||
session_token: token,
|
||||
};
|
||||
|
||||
req.headers["Content-Length"] = Buffer.byteLength(
|
||||
JSON.stringify(req.body),
|
||||
).toString();
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
const appendJwt: RequestHandler = async (req, _res, next) => {
|
||||
if (jwt) {
|
||||
req.headers["Authorization"] = `Bearer ${jwt}`;
|
||||
delete req.headers["cookie"];
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
const loginCallback: RequestHandler = async (req, res, _next) => {
|
||||
const query = req.query;
|
||||
|
||||
if (query.token && query.jwt) {
|
||||
token = query.token as string;
|
||||
jwt = query.jwt as string;
|
||||
await saveAuth(query.token as string, query.jwt as string);
|
||||
}
|
||||
|
||||
const errorParams = new URLSearchParams();
|
||||
if (query.error) {
|
||||
errorParams.set("error", query.error as string);
|
||||
}
|
||||
if (query.code) {
|
||||
errorParams.set("code", query.code as string);
|
||||
}
|
||||
|
||||
res.redirect(`${AUTH_CALLBACK_UI_PATH}?${errorParams.toString()}`);
|
||||
};
|
||||
|
||||
const loginTrigger: RequestHandler = async (req, res, _next) => {
|
||||
const query = req.query;
|
||||
const protocol = req.secure ? "https" : "http";
|
||||
const host = req.headers.host;
|
||||
|
||||
if (!host) {
|
||||
res.redirect(`${AUTH_CALLBACK_API_PATH}?error=Missing%20Host`);
|
||||
return;
|
||||
}
|
||||
|
||||
const callbackUrl = `${protocol}://${host}${AUTH_CALLBACK_API_PATH}`;
|
||||
|
||||
const params = new URLSearchParams({
|
||||
provider: query.provider as string,
|
||||
returnUrl: encodeURIComponent(callbackUrl),
|
||||
});
|
||||
|
||||
res.redirect(`${AUTH_SERVER_URL}/login?${params.toString()}`);
|
||||
};
|
||||
|
||||
app.use(AUTH_TRIGGER_API_PATH, loginTrigger);
|
||||
|
||||
app.use(AUTH_CALLBACK_API_PATH, loginCallback);
|
||||
|
||||
app.use("/api/.auth", appendAuth, authProxy);
|
||||
|
||||
app.use("/api/.refine", projectIdAppender, appendJwt, refineProxy);
|
||||
};
|
||||
46
packages/devtools-server/src/serve-ws.ts
Normal file
46
packages/devtools-server/src/serve-ws.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import WebSocket from "ws";
|
||||
import { SERVER_PORT } from "./constants";
|
||||
import { bold, cyanBright } from "chalk";
|
||||
import type http from "http";
|
||||
|
||||
export const serveWs = (
|
||||
server: http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>,
|
||||
onError: () => void,
|
||||
) => {
|
||||
const ws = new WebSocket.Server({ server }).on("error", (error: any) => {
|
||||
if (error?.code === "EADDRINUSE") {
|
||||
console.error(
|
||||
`\n${cyanBright.bold("\u2717 ")}${bold(
|
||||
"Refine Devtools server",
|
||||
)} (websocket) failed to start. Port ${SERVER_PORT} is already in use.\n`,
|
||||
);
|
||||
} else {
|
||||
console.error(
|
||||
`\n${cyanBright.bold("\u2717 ")}${bold("error from refine devtools")}`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
ws.close(() => {
|
||||
if (__DEVELOPMENT__) {
|
||||
console.log("Process terminated");
|
||||
}
|
||||
});
|
||||
onError();
|
||||
});
|
||||
|
||||
ws.on("connection", (client) => {
|
||||
client.on("close", () => {
|
||||
client.terminate();
|
||||
});
|
||||
});
|
||||
|
||||
process.on("SIGTERM", () => {
|
||||
ws.close(() => {
|
||||
if (__DEVELOPMENT__) {
|
||||
console.log("Process terminated");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return ws;
|
||||
};
|
||||
62
packages/devtools-server/src/setup-server.ts
Normal file
62
packages/devtools-server/src/setup-server.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import type { Express } from "express";
|
||||
import { SERVER_PORT } from "./constants";
|
||||
import { bold, cyanBright } from "chalk";
|
||||
import http from "http";
|
||||
|
||||
export const setupServer = (app: Express, onError: () => void) => {
|
||||
const server = http.createServer(app);
|
||||
|
||||
server
|
||||
.on("error", (error: any) => {
|
||||
if (error?.code === "EADDRINUSE") {
|
||||
console.error(
|
||||
`\n${cyanBright.bold("\u2717 ")}${bold(
|
||||
"Refine Devtools server",
|
||||
)} (http) failed to start. Port ${SERVER_PORT} is already in use.\n`,
|
||||
);
|
||||
console.info(
|
||||
`${cyanBright.bold(
|
||||
"\u2139 ",
|
||||
)}You can change the port by setting the ${bold(
|
||||
"REFINE_DEVTOOLS_PORT",
|
||||
)} environment variable.`,
|
||||
);
|
||||
} else {
|
||||
console.error(
|
||||
`\n${cyanBright.bold("\u2717 ")}${bold(
|
||||
"error from Refine Devtools",
|
||||
)}`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
server.close(() => {
|
||||
if (__DEVELOPMENT__) {
|
||||
console.log("Process terminated");
|
||||
}
|
||||
});
|
||||
onError();
|
||||
})
|
||||
.on("listening", () => {
|
||||
console.log(
|
||||
`\n${cyanBright.bold("\u2713 ")}${bold(
|
||||
"Refine Devtools",
|
||||
)} is running at port ${cyanBright.bold(SERVER_PORT)}\n`,
|
||||
);
|
||||
});
|
||||
|
||||
process.on("SIGTERM", () => {
|
||||
server.close(() => {
|
||||
if (__DEVELOPMENT__) {
|
||||
console.log("Process terminated");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
server.listen(SERVER_PORT, undefined, undefined, () => {
|
||||
if (__DEVELOPMENT__) {
|
||||
console.log(`Server started on PORT ${SERVER_PORT}`);
|
||||
}
|
||||
});
|
||||
|
||||
return server;
|
||||
};
|
||||
Reference in New Issue
Block a user