feat(medusa,dashboard,admin-sdk): Run admin dashboard from Medusa instance (#7330)
This commit is contained in:
@@ -0,0 +1 @@
|
||||
# `@medusajs/admin-sdk`
|
||||
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "@medusajs/admin-sdk",
|
||||
"version": "0.0.1",
|
||||
"description": "Admin SDK for Medusa.",
|
||||
"author": "Kasper Kristensen <kasper@medusajs.com>",
|
||||
"scripts": {
|
||||
"build": "tsup && copyfiles -f ./src/index.html ./src/entry.tsx ./src/index.css ./dist"
|
||||
},
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"files": [
|
||||
"dist",
|
||||
"package.json"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@medusajs/types": "^1.11.16",
|
||||
"@types/compression": "^1.7.5",
|
||||
"@types/connect-history-api-fallback": "^1.5.4",
|
||||
"copyfiles": "^2.4.1",
|
||||
"express": "^4.18.2",
|
||||
"tsup": "^8.0.1",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@medusajs/admin-vite-plugin": "0.0.1",
|
||||
"@medusajs/dashboard": "0.0.1",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"commander": "^11.1.0",
|
||||
"compression": "^1.7.4",
|
||||
"connect-history-api-fallback": "^2.0.0",
|
||||
"deepmerge": "^4.3.1",
|
||||
"glob": "^7.1.6",
|
||||
"postcss": "^8.4.32",
|
||||
"tailwindcss": "^3.3.6",
|
||||
"vite": "^5.2.11",
|
||||
"vite-plugin-node-polyfills": "^0.21.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"express": "^4.18.2",
|
||||
"react-dom": "^18.0.0"
|
||||
},
|
||||
"packageManager": "yarn@3.2.1"
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import App from "@medusajs/dashboard"
|
||||
import React from "react"
|
||||
import { createRoot } from "react-dom/client"
|
||||
|
||||
import "./index.css"
|
||||
|
||||
const container = document.getElementById("root")
|
||||
const root = createRoot(container!)
|
||||
root.render(<App />)
|
||||
@@ -0,0 +1,5 @@
|
||||
@import "@medusajs/dashboard/css";
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
|
||||
<link rel="icon" href="data:," data-placeholder-favicon />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="./entry.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,5 @@
|
||||
export { build } from "./lib/build"
|
||||
export { develop } from "./lib/develop"
|
||||
export { serve } from "./lib/serve"
|
||||
|
||||
export * from "./types"
|
||||
@@ -0,0 +1,17 @@
|
||||
import { BundlerOptions } from "../types"
|
||||
import { getViteConfig } from "./config"
|
||||
|
||||
export async function build(options: BundlerOptions) {
|
||||
const vite = await import("vite")
|
||||
|
||||
const viteConfig = await getViteConfig(options)
|
||||
|
||||
try {
|
||||
await vite.build(
|
||||
vite.mergeConfig(viteConfig, { mode: "production", logLevel: "silent" })
|
||||
)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
throw new Error("Failed to build admin panel")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
import path from "path"
|
||||
import { Config } from "tailwindcss"
|
||||
import type { InlineConfig } from "vite"
|
||||
import { nodePolyfills } from "vite-plugin-node-polyfills"
|
||||
|
||||
import { BundlerOptions } from "../types"
|
||||
|
||||
export async function getViteConfig(
|
||||
options: BundlerOptions
|
||||
): Promise<InlineConfig> {
|
||||
const { searchForWorkspaceRoot } = await import("vite")
|
||||
const { default: react } = await import("@vitejs/plugin-react")
|
||||
const { default: inject } = await import("@medusajs/admin-vite-plugin")
|
||||
|
||||
const getPort = await import("get-port")
|
||||
const hmrPort = await getPort.default()
|
||||
|
||||
const root = path.resolve(__dirname, "./")
|
||||
|
||||
return {
|
||||
root: path.resolve(__dirname, "./"),
|
||||
base: options.path,
|
||||
build: {
|
||||
emptyOutDir: true,
|
||||
outDir: path.resolve(process.cwd(), options.outDir),
|
||||
},
|
||||
optimizeDeps: {
|
||||
include: ["@medusajs/dashboard", "react-dom/client"],
|
||||
},
|
||||
define: {
|
||||
__BASE__: JSON.stringify(options.path),
|
||||
/**
|
||||
* TODO: Accept backend url from config to support hosting the admin elsewhere.
|
||||
* The empty string should be the default value, as that ensures that requests
|
||||
* are made to the server that serves the admin dashboard.
|
||||
*/
|
||||
__BACKEND_URL__: JSON.stringify(""),
|
||||
},
|
||||
server: {
|
||||
open: true,
|
||||
fs: {
|
||||
allow: [
|
||||
searchForWorkspaceRoot(process.cwd()),
|
||||
path.resolve(__dirname, "../../medusa"),
|
||||
path.resolve(__dirname, "../../app"),
|
||||
],
|
||||
},
|
||||
hmr: {
|
||||
port: hmrPort,
|
||||
},
|
||||
middlewareMode: true,
|
||||
},
|
||||
css: {
|
||||
postcss: {
|
||||
plugins: [
|
||||
require("tailwindcss")({
|
||||
config: createTailwindConfig(root),
|
||||
}),
|
||||
],
|
||||
},
|
||||
},
|
||||
/**
|
||||
* TODO: Remove polyfills, they are currently only required for the
|
||||
* `axios` dependency in the dashboard. Once we have the new SDK,
|
||||
* we should remove this, and leave it up to the user to include
|
||||
* polyfills if they need them.
|
||||
*/
|
||||
plugins: [
|
||||
react(),
|
||||
inject(),
|
||||
nodePolyfills({
|
||||
include: ["crypto", "util", "stream"],
|
||||
}),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
function createTailwindConfig(entry: string) {
|
||||
const root = path.join(entry, "**/*.{js,ts,jsx,tsx}")
|
||||
const html = path.join(entry, "index.html")
|
||||
|
||||
let dashboard = ""
|
||||
|
||||
try {
|
||||
dashboard = path.join(
|
||||
path.dirname(require.resolve("@medusajs/dashboard")),
|
||||
"**/*.{js,ts,jsx,tsx}"
|
||||
)
|
||||
} catch (_e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
let ui: string = ""
|
||||
|
||||
try {
|
||||
ui = path.join(
|
||||
path.dirname(require.resolve("@medusajs/ui")),
|
||||
"**/*.{js,ts,jsx,tsx}"
|
||||
)
|
||||
} catch (_e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
const config: Config = {
|
||||
presets: [require("@medusajs/ui-preset")],
|
||||
content: [html, root, dashboard, ui],
|
||||
darkMode: "class",
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import express from "express"
|
||||
|
||||
import { BundlerOptions } from "../types"
|
||||
import { getViteConfig } from "./config"
|
||||
|
||||
const router = express.Router()
|
||||
|
||||
export async function develop(options: BundlerOptions) {
|
||||
const vite = await import("vite")
|
||||
|
||||
try {
|
||||
const viteConfig = await getViteConfig(options)
|
||||
const server = await vite.createServer(
|
||||
vite.mergeConfig(viteConfig, { logLevel: "info", mode: "development" })
|
||||
)
|
||||
|
||||
router.use(server.middlewares)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
throw new Error("Could not start development server")
|
||||
}
|
||||
|
||||
return router
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { Request, Response, Router, static as static_ } from "express"
|
||||
import fs from "fs"
|
||||
import { ServerResponse } from "http"
|
||||
import path from "path"
|
||||
|
||||
type ServeOptions = {
|
||||
outDir: string
|
||||
}
|
||||
|
||||
const router = Router()
|
||||
|
||||
export async function serve(options: ServeOptions) {
|
||||
const htmlPath = path.resolve(options.outDir, "index.html")
|
||||
|
||||
/**
|
||||
* The admin UI should always be built at this point, but in the
|
||||
* rare case that another plugin terminated a previous startup, the admin
|
||||
* may not have been built correctly. Here we check if the admin UI
|
||||
* build files exist, and if not, we throw an error, providing the
|
||||
* user with instructions on how to fix their build.
|
||||
*/
|
||||
|
||||
const indexExists = fs.existsSync(htmlPath)
|
||||
|
||||
if (!indexExists) {
|
||||
throw new Error(
|
||||
`Could not find the admin UI build files. Please run "medusa-admin build" or enable "autoRebuild" in the plugin options to build the admin UI.`
|
||||
)
|
||||
}
|
||||
|
||||
const html = fs.readFileSync(htmlPath, "utf-8")
|
||||
|
||||
const sendHtml = (_req: Request, res: Response) => {
|
||||
res.setHeader("Cache-Control", "no-cache")
|
||||
res.setHeader("Vary", "Origin, Cache-Control")
|
||||
res.send(html)
|
||||
}
|
||||
|
||||
const setStaticHeaders = (res: ServerResponse) => {
|
||||
res.setHeader("Cache-Control", "max-age=31536000, immutable")
|
||||
res.setHeader("Vary", "Origin, Cache-Control")
|
||||
}
|
||||
|
||||
router.get("/", sendHtml)
|
||||
router.use(
|
||||
static_(options.outDir, {
|
||||
setHeaders: setStaticHeaders,
|
||||
})
|
||||
)
|
||||
router.get(`/*`, sendHtml)
|
||||
|
||||
return router
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { AdminOptions } from "@medusajs/types"
|
||||
|
||||
export type BundlerOptions = Required<Pick<AdminOptions, "outDir" | "path">> &
|
||||
Pick<AdminOptions, "vite">
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"jsx": "react",
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"target": "ES2020",
|
||||
"module": "ES2020",
|
||||
"moduleResolution": "bundler",
|
||||
"skipLibCheck": true,
|
||||
"isolatedModules": true,
|
||||
"strict": true,
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"noEmit": true,
|
||||
"noUnusedLocals": true,
|
||||
"esModuleInterop": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["tsup.config.ts", "node_modules", "dist"]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { defineConfig } from "tsup"
|
||||
|
||||
export default defineConfig({
|
||||
entry: ["src/index.ts"],
|
||||
format: ["cjs"],
|
||||
dts: true,
|
||||
clean: true,
|
||||
})
|
||||
Reference in New Issue
Block a user