This commit is contained in:
Stefan Pejcic
2024-05-08 19:58:53 +02:00
parent 440d98beff
commit 8595a9f4e5
2479 changed files with 591504 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
#!/usr/bin/env node
import { server } from "./index";
server();

View File

@@ -0,0 +1,31 @@
<!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" />
<meta name="description" content="Web site created using refine" />
<!--
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 start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
--></body>
</html>

View 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");
}

View 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",
},
});

View File

@@ -0,0 +1,12 @@
export const DEFAULT_WS_PORT = 5002;
export const DEFAULT_SERVER_PORT = 5001;
export const WS_PORT = DEFAULT_WS_PORT;
export const SERVER_PORT = DEFAULT_SERVER_PORT;
export const REFINE_API_URL = __DEVELOPMENT__
? "https://develop.cloud.refine.dev"
: "https://cloud2.refine.dev";
export const FEED_MD_URL =
"https://raw.githubusercontent.com/refinedev/refine/master/packages/devtools-server/FEED.md";

View File

@@ -0,0 +1,34 @@
import {
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,
};
};

View File

@@ -0,0 +1 @@
declare const __DEVELOPMENT__: boolean;

View File

@@ -0,0 +1,68 @@
import fetch from "node-fetch";
import matter from "gray-matter";
import { marked } from "marked";
import sanitizeHtml from "sanitize-html";
import { 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 [];
}
};

View File

@@ -0,0 +1,133 @@
import express from "express";
import { cyanBright, bold } from "chalk";
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 { Activity, createDb } from "./create-db";
import { serveApi } from "./serve-api";
import { SERVER_PORT } from "./constants";
import { serveProxy } from "./serve-proxy";
import { serveOpenInEditor } from "./serve-open-in-editor";
type Options = {
projectPath?: string;
};
export const server = async ({ projectPath = process.cwd() }: Options = {}) => {
const app = express();
const ws = serveWs();
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_LOGIN_SUCCESS, () => {
ws.clients.forEach((c) => {
send(c as any, DevtoolsEvent.DEVTOOLS_RELOAD_AFTER_LOGIN, {});
});
});
// 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);
setupServer(app);
serveApi(app, db);
serveProxy(app);
serveOpenInEditor(app, projectPath);
};

View File

@@ -0,0 +1,28 @@
import { 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 [];
}
};

View File

@@ -0,0 +1,513 @@
import { 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`,
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`,
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),
);
};

View File

@@ -0,0 +1,3 @@
export const getChangelog = (packageName: string) => {
return packageName.replace("@refinedev/", "https://c.refine.dev/");
};

View File

@@ -0,0 +1,3 @@
export const getDocumentation = (packageName: string) => {
return "https://refine.dev/docs/api-reference/general-concepts/";
};

View File

@@ -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;
}
};

View File

@@ -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;
}
};

View File

@@ -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 };
}
};

View File

@@ -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;
};

View File

@@ -0,0 +1,22 @@
import preferredPM from "preferred-pm";
import execa from "execa";
export const updatePackage = async (
packages: string[],
projectPath: string = process.cwd(),
) => {
try {
const { name: pm } = (await preferredPM(projectPath)) ?? {
name: "npm",
};
const { failed } = await execa(pm ?? "npm", [
"install",
...packages.map((p) => `${p}@latest`),
]);
return !failed;
} catch (error) {
return false;
}
};

View File

@@ -0,0 +1,25 @@
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;
}
};

View File

@@ -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;
}
};

View File

@@ -0,0 +1,46 @@
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;
};

View File

@@ -0,0 +1,92 @@
import { namedTypes } from "ast-types";
import {
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();
}

View 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;
}
};

View File

@@ -0,0 +1,32 @@
import fs from "fs";
import path from "path";
import { debounce } from "lodash";
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;

View File

@@ -0,0 +1,169 @@
import type { Express } from "express";
import { json } from "express";
import uniq from "lodash/uniq";
import {
AvailablePackageType,
Feed,
PackageType,
} from "@refinedev/devtools-shared";
import { 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 projectId = await getProjectIdFromPackageJson();
if (projectId) {
res.status(200).json({ projectId });
return;
} else if (projectId === false) {
res.status(404).json({ projectId: null });
return;
} else {
res.status(500).json({ projectId: null });
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;
} else {
res.status(500).json({
success: false,
error: "Failed to update project ID",
});
return;
}
});
};

View 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"));
});
};

View File

@@ -0,0 +1,16 @@
import { 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);
});
};

View File

@@ -0,0 +1,193 @@
import { readJSON, writeJSON } from "fs-extra";
import { FrontendApi } from "@ory/client";
import { createProxyMiddleware, Options } from "http-proxy-middleware";
import path from "path";
import { REFINE_API_URL, SERVER_PORT } from "./constants";
import { getProjectIdFromPackageJson } from "./project-id/get-project-id-from-package-json";
import type { Express, RequestHandler } from "express";
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 restream: Options["onProxyReq"] = function (proxyReq, req) {
if (req.body) {
const bodyData = JSON.stringify(req.body);
// incase if content-type is application/x-www-form-urlencoded -> we need to change to application/json
proxyReq.setHeader("Content-Type", "application/json");
proxyReq.setHeader("Content-Length", Buffer.byteLength(bodyData));
// stream the content
proxyReq.write(bodyData);
}
};
const tokenize = async (token: string) => {
try {
const ORY_URL = `${REFINE_API_URL}/.auth`;
const ory = new FrontendApi({
isJsonMime: () => true,
basePath: ORY_URL,
baseOptions: {
withCredentials: true,
},
});
const { data } = await ory.toSession({
xSessionToken: token,
tokenizeAs: "jwt_template_1",
});
return data?.tokenized;
} catch (err) {
//
}
return undefined;
};
const saveAuth = async (token?: string, jwt?: string) => {
try {
writeJSON(path.join(__dirname, "..", ".persist.json"), {
token: token,
jwt: jwt,
});
} catch (error) {
//
}
};
const loadAuth = async () => {
try {
const persist = await readJSON(
path.join(__dirname, "..", ".persist.json"),
);
return persist as { token?: string; jwt?: string };
} catch (error) {
//
}
return {
token: undefined,
jwt: undefined,
};
};
const handleLogoutToken: (
token?: string,
) => NonNullable<Options["onProxyReq"]> = (token) => {
return function (proxyReq, req) {
if (req.url.includes("self-service/logout/api")) {
const bodyData = JSON.stringify({
session_token: token,
});
proxyReq.setHeader("Content-Length", Buffer.byteLength(bodyData));
// stream the content
proxyReq.write(bodyData);
}
};
};
const handleSignInCallbacks: (
onToken: (token?: string, jwt?: string) => void,
) => NonNullable<Options["onProxyRes"]> = (onToken) => {
return function (proxyRes, req, res) {
let body = "";
proxyRes.on("data", (chunk) => {
body += chunk;
});
proxyRes.on("end", () => {
let sessionToken: string | undefined = undefined;
try {
const parsed = JSON.parse(body);
sessionToken = parsed.session_token;
} catch (err) {
//
}
if (!sessionToken) {
if (body?.includes?.("an+account+with+the+same+identifier")) {
res.redirect(
"/after-login?error=An+account+with+the+same+identifier+exists+already",
);
return;
}
res.redirect("/after-login?error=Invalid-session-token");
return;
}
// After grabbing the session_token, convert it to JWT, then redirect to /after-login
tokenize(sessionToken).then((tokenized) => {
onToken(sessionToken, tokenized ?? "");
res.redirect(`/after-login`);
});
});
};
};
export const serveProxy = async (app: Express) => {
let { token, jwt } = await loadAuth();
const authProxy = createProxyMiddleware({
target: REFINE_API_URL,
// secure: false,
changeOrigin: true,
pathRewrite: { "^/api/.auth": "/.auth" },
cookieDomainRewrite: {
"refine.dev": "",
},
logLevel: __DEVELOPMENT__ ? "debug" : "silent",
headers: {
"auth-base-url-rewrite": `http://localhost:${SERVER_PORT}/api/.auth`,
},
selfHandleResponse: true,
onProxyReq: (proxyReq, req, ...rest) => {
if (token) {
proxyReq.setHeader("X-Session-Token", token ?? "");
handleLogoutToken(token)(proxyReq, req, ...rest);
}
},
onProxyRes: (proxyRes, req, res) => {
if (req.url.includes("self-service/methods/oidc/callback")) {
return handleSignInCallbacks((_token, _jwt) => {
token = _token;
jwt = _jwt;
saveAuth(token, jwt);
})(proxyRes, req, res);
} else {
res.writeHead(proxyRes.statusCode || 500, proxyRes.headers);
proxyRes.pipe(res, { end: true });
}
},
});
app.use("/api/.auth", authProxy);
const refineApiProxy = createProxyMiddleware({
target: REFINE_API_URL,
secure: false,
changeOrigin: true,
logLevel: __DEVELOPMENT__ ? "debug" : "silent",
pathRewrite: { "^/api/.refine": "/.refine" },
onProxyReq: (proxyReq, ...rest) => {
if (jwt) {
proxyReq.setHeader("Authorization", `Bearer ${jwt}`);
proxyReq.removeHeader("cookie");
}
restream(proxyReq, ...rest);
},
});
app.use("/api/.refine", projectIdAppender, refineApiProxy);
};

View File

@@ -0,0 +1,51 @@
import WebSocket from "ws";
import { SERVER_PORT, WS_PORT } from "./constants";
import { DevtoolsEvent, send } from "@refinedev/devtools-shared";
import { bold, cyanBright } from "chalk";
export const serveWs = () => {
const ws = new WebSocket.Server({ port: WS_PORT }).on(
"error",
(error: any) => {
if (error?.code === "EADDRINUSE") {
console.error(
`\n${cyanBright.bold("\u2717 ")}${bold(
"refine devtools",
)} failed to start. Port ${WS_PORT} is already in use, please make sure no other devtools server is running\n`,
);
} else {
console.error(
`\n${cyanBright.bold("\u2717 ")}${bold(
"error from refine devtools",
)}`,
error,
);
}
process.exit(1);
},
);
ws.on("connection", (client) => {
if (__DEVELOPMENT__) {
console.log(`WebSocket server started on PORT ${WS_PORT}`);
}
// send client the devtools client url
send(client as any, DevtoolsEvent.DEVTOOLS_HANDSHAKE, {
url: `http://localhost:${SERVER_PORT}`,
});
client.on("close", () => {
client.terminate();
});
});
process.on("SIGTERM", () => {
ws.close(() => {
if (__DEVELOPMENT__) {
console.log("Process terminated");
}
});
});
return ws;
};

View File

@@ -0,0 +1,44 @@
import type { Express } from "express";
import { SERVER_PORT } from "./constants";
import { bold, cyanBright } from "chalk";
export const setupServer = (app: Express) => {
const server = app
.listen(SERVER_PORT, () => {
if (__DEVELOPMENT__) {
console.log(`Server started on PORT ${SERVER_PORT}`);
}
})
.on("error", (error: any) => {
if (error?.code === "EADDRINUSE") {
console.error(
`\n${cyanBright.bold("\u2717 ")}${bold(
"refine devtools",
)} failed to start. Port ${SERVER_PORT} is already in use, please make sure no other devtools server is running\n`,
);
} else {
console.error(
`\n${cyanBright.bold("\u2717 ")}${bold(
"error from refine devtools",
)}`,
error,
);
}
process.exit(1);
})
.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");
}
});
});
};