feat(admin): Improve DX for deploying admin externally (#3418)
* init deploy command * add include flag * add 'shortcut' flag * add dev command, fix var replacement, change default behaviour * cleanup params of build command * fix defaults when using the plugin to serve admin * add changeset * fix globals * update README * throw error on no build found --------- Co-authored-by: Oliver Windall Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
1 parent
9ad15d3a88
commit
8a7421db5b
20 files changed
+358
-85
No files matched your search
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/admin-ui": patch
|
||||||
|
"@medusajs/admin": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
feat(admin,admin-ui): Updates the default behaviour of the plugin, and makes building for external deployment easier
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
|
import dns from "dns"
|
||||||
import fse from "fs-extra"
|
import fse from "fs-extra"
|
||||||
import { resolve } from "path"
|
import { resolve } from "path"
|
||||||
import vite from "vite"
|
import vite from "vite"
|
||||||
import { AdminBuildConfig } from "./types"
|
import { AdminBuildConfig } from "./types"
|
||||||
import { getCustomViteConfig } from "./utils"
|
import { AdminDevConfig } from "./types/dev"
|
||||||
|
import { getCustomViteConfig, getCustomViteDevConfig } from "./utils"
|
||||||
|
|
||||||
async function build(options?: AdminBuildConfig) {
|
async function build(options?: AdminBuildConfig) {
|
||||||
const config = getCustomViteConfig(options)
|
const config = getCustomViteConfig(options)
|
||||||
@@ -25,4 +27,16 @@ async function clean() {
|
|||||||
throw new Error("Not implemented")
|
throw new Error("Not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
export { build, watch, clean }
|
async function dev(options: AdminDevConfig) {
|
||||||
|
// Resolve localhost for Node v16 and older.
|
||||||
|
// @see https://vitejs.dev/config/server-options.html#server-host.
|
||||||
|
dns.setDefaultResultOrder("verbatim")
|
||||||
|
|
||||||
|
const server = await vite.createServer(getCustomViteDevConfig(options))
|
||||||
|
await server.listen()
|
||||||
|
|
||||||
|
server.printUrls()
|
||||||
|
}
|
||||||
|
|
||||||
|
export { build, dev, watch, clean }
|
||||||
|
export type { AdminBuildConfig, AdminDevConfig }
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export type AdminDevConfig = {
|
||||||
|
backend?: string
|
||||||
|
port?: number
|
||||||
|
}
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
export * from "./build"
|
export * from "./build"
|
||||||
|
export * from "./dev"
|
||||||
export * from "./misc"
|
export * from "./misc"
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
import { Base } from "../types"
|
import { Base } from "../types"
|
||||||
|
|
||||||
export const formatBase = <T extends string>(base: T): Base<T> => {
|
export const formatBase = <T extends string>(base?: T): Base<T> => {
|
||||||
|
if (!base) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
return `/${base}/`
|
return `/${base}/`
|
||||||
}
|
}
|
||||||
@@ -10,9 +10,7 @@ export const getCustomViteConfig = (config: AdminBuildConfig): InlineConfig => {
|
|||||||
const uiPath = resolve(__dirname, "..", "..", "ui")
|
const uiPath = resolve(__dirname, "..", "..", "ui")
|
||||||
|
|
||||||
const globalReplacements = () => {
|
const globalReplacements = () => {
|
||||||
const base = globals.base || "app"
|
let backend = undefined
|
||||||
|
|
||||||
let backend = "/"
|
|
||||||
|
|
||||||
if (globals.backend) {
|
if (globals.backend) {
|
||||||
try {
|
try {
|
||||||
@@ -26,10 +24,12 @@ export const getCustomViteConfig = (config: AdminBuildConfig): InlineConfig => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
const global = {}
|
||||||
__BASE__: JSON.stringify(`/${base}`),
|
|
||||||
__MEDUSA_BACKEND_URL__: JSON.stringify(backend),
|
global["__BASE__"] = JSON.stringify(globals.base ? `/${globals.base}` : "/")
|
||||||
}
|
global["__MEDUSA_BACKEND_URL__"] = JSON.stringify(backend ? backend : "/")
|
||||||
|
|
||||||
|
return global
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildConfig = (): BuildOptions => {
|
const buildConfig = (): BuildOptions => {
|
||||||
@@ -41,7 +41,7 @@ export const getCustomViteConfig = (config: AdminBuildConfig): InlineConfig => {
|
|||||||
/**
|
/**
|
||||||
* Default build directory is at the root of the `@medusajs/admin-ui` package.
|
* Default build directory is at the root of the `@medusajs/admin-ui` package.
|
||||||
*/
|
*/
|
||||||
destDir = resolve(__dirname, "..", "..", "build")
|
destDir = resolve(process.cwd(), "build")
|
||||||
} else {
|
} else {
|
||||||
/**
|
/**
|
||||||
* If a custom build directory is specified, it is resolved relative to the
|
* If a custom build directory is specified, it is resolved relative to the
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import react from "@vitejs/plugin-react"
|
||||||
|
import { resolve } from "path"
|
||||||
|
import { InlineConfig } from "vite"
|
||||||
|
import { AdminDevConfig } from "../types/dev"
|
||||||
|
|
||||||
|
export const getCustomViteDevConfig = ({
|
||||||
|
backend = "http://localhost:9000",
|
||||||
|
port = 7001,
|
||||||
|
}: AdminDevConfig): InlineConfig => {
|
||||||
|
const uiPath = resolve(__dirname, "..", "..", "ui")
|
||||||
|
|
||||||
|
return {
|
||||||
|
define: {
|
||||||
|
__BASE__: JSON.stringify("/"),
|
||||||
|
__MEDUSA_BACKEND_URL__: JSON.stringify(backend),
|
||||||
|
},
|
||||||
|
plugins: [react()],
|
||||||
|
root: uiPath,
|
||||||
|
mode: "development",
|
||||||
|
server: {
|
||||||
|
port,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
export * from "./format-base"
|
export * from "./format-base"
|
||||||
export * from "./get-custom-vite-config"
|
export * from "./get-custom-vite-config"
|
||||||
|
export * from "./get-custom-vite-dev-config"
|
||||||
@@ -64,12 +64,12 @@ module.exports = {
|
|||||||
|
|
||||||
The plugin can be configured with the following options:
|
The plugin can be configured with the following options:
|
||||||
|
|
||||||
| Option | Type | Description | Default |
|
| Option | Type | Description | Default |
|
||||||
| --------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
|
| ------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
|
||||||
| `serve` | `boolean?` | Whether to serve the admin dashboard or not. | `true` |
|
| `serve` | `boolean?` | Whether to serve the admin dashboard or not. | `true` |
|
||||||
| `path` | `string?` | The path the admin server should run on. Should not be prefixed or suffixed with a slash. Cannot be one of the reserved paths: `"admin"` and `"store"`. | `"app"` |
|
| `path` | `string?` | The path the admin server should run on. Should not be prefixed or suffixed with a slash. Cannot be one of the reserved paths: `"admin"` and `"store"`. | `"app"` |
|
||||||
| `outDir` | `string?` | Optional path for where to output the admin build files | `undefined` |
|
| `outDir` | `string?` | Optional path for where to output the admin build files | `undefined` |
|
||||||
| `backend` | `string?` | URL to server. Should only be set if you plan on hosting the admin dashboard separately from your server | `undefined` |
|
| `autoRebuild` | `boolean?` | Decides whether the admin UI should be rebuild if any changes or a missing build is detected during server startup | `false` |
|
||||||
|
|
||||||
**Hint**: You can import the PluginOptions type for inline documentation for the different options:
|
**Hint**: You can import the PluginOptions type for inline documentation for the different options:
|
||||||
|
|
||||||
@@ -91,9 +91,9 @@ module.exports = {
|
|||||||
|
|
||||||
## Building the admin dashboard
|
## Building the admin dashboard
|
||||||
|
|
||||||
The admin will be built automatically the first time you start your server. Any subsequent changes to the plugin options will result in a rebuild of the admin dashboard.
|
The admin will be built automatically the first time you start your server if you have enabled `autoRebuild`. Any subsequent changes to the plugin options will result in a rebuild of the admin dashboard.
|
||||||
|
|
||||||
You may need to manually trigger a rebuild sometimes, for example after you have upgraded to a newer version of `@medusajs/admin`. You can do so by adding the following script to your `package.json`:
|
You may need to manually trigger a rebuild sometimes, for example after you have upgraded to a newer version of `@medusajs/admin`, or if you have disabled `autoRebuild`. You can do so by adding the following script to your `package.json`:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -28,11 +28,13 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@medusajs/admin-ui": "*",
|
"@medusajs/admin-ui": "*",
|
||||||
"commander": "^10.0.0",
|
"commander": "^10.0.0",
|
||||||
|
"dotenv": "^16.0.3",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"fs-extra": "^11.1.0",
|
"fs-extra": "^11.1.0",
|
||||||
"medusa-core-utils": "*",
|
"medusa-core-utils": "*",
|
||||||
"ora": "5.4.0",
|
"ora": "5.4.0",
|
||||||
"picocolors": "^1.0.0"
|
"picocolors": "^1.0.0",
|
||||||
|
"ts-dedent": "^2.2.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@medusajs/medusa": "*"
|
"@medusajs/medusa": "*"
|
||||||
|
|||||||
@@ -13,21 +13,16 @@ export default function (_rootDirectory: string, options: PluginOptions) {
|
|||||||
|
|
||||||
if (serve) {
|
if (serve) {
|
||||||
let buildPath: string
|
let buildPath: string
|
||||||
let htmlPath: string
|
|
||||||
|
|
||||||
|
// If an outDir is provided we use that, otherwise we default to "build".
|
||||||
if (outDir) {
|
if (outDir) {
|
||||||
buildPath = resolve(process.cwd(), outDir)
|
buildPath = resolve(process.cwd(), outDir)
|
||||||
htmlPath = resolve(buildPath, "index.html")
|
|
||||||
} else {
|
} else {
|
||||||
buildPath = resolve(
|
buildPath = resolve(process.cwd(), "build")
|
||||||
require.resolve("@medusajs/admin-ui"),
|
|
||||||
"..",
|
|
||||||
"..",
|
|
||||||
"build"
|
|
||||||
)
|
|
||||||
htmlPath = resolve(buildPath, "index.html")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const htmlPath = resolve(buildPath, "index.html")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The admin UI should always be built at this point, but in the
|
* The admin UI should always be built at this point, but in the
|
||||||
* rare case that another plugin terminated a previous startup, the admin
|
* rare case that another plugin terminated a previous startup, the admin
|
||||||
@@ -35,14 +30,17 @@ export default function (_rootDirectory: string, options: PluginOptions) {
|
|||||||
* build files exist, and if not, we throw an error, providing the
|
* build files exist, and if not, we throw an error, providing the
|
||||||
* user with instructions on how to fix their build.
|
* user with instructions on how to fix their build.
|
||||||
*/
|
*/
|
||||||
try {
|
|
||||||
fse.ensureFileSync(htmlPath)
|
const indexExists = fse.existsSync(htmlPath)
|
||||||
} catch (_err) {
|
|
||||||
|
if (!indexExists) {
|
||||||
reporter.panic(
|
reporter.panic(
|
||||||
new Error(
|
new Error(
|
||||||
`Could not find the admin UI build files. Please run ${colors.bold(
|
`Could not find the admin UI build files. Please run ${colors.bold(
|
||||||
"`medusa-admin build`"
|
"`medusa-admin build`"
|
||||||
)} to build the admin UI.`
|
)} or enable ${colors.bold(
|
||||||
|
`autoRebuild`
|
||||||
|
)} in the plugin options to build the admin UI.`
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,48 +1,100 @@
|
|||||||
import { build as buildAdmin } from "@medusajs/admin-ui"
|
import { AdminBuildConfig, build as buildAdmin } from "@medusajs/admin-ui"
|
||||||
|
import dotenv from "dotenv"
|
||||||
|
import fse from "fs-extra"
|
||||||
import ora from "ora"
|
import ora from "ora"
|
||||||
import { EOL } from "os"
|
import { EOL } from "os"
|
||||||
|
import { resolve } from "path"
|
||||||
import { loadConfig, reporter, validatePath } from "../utils"
|
import { loadConfig, reporter, validatePath } from "../utils"
|
||||||
|
|
||||||
type BuildArgs = {
|
type BuildArgs = {
|
||||||
|
deployment?: boolean
|
||||||
outDir?: string
|
outDir?: string
|
||||||
backend?: string
|
backend?: string
|
||||||
path?: string
|
include?: string[]
|
||||||
|
includeDist?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
let ENV_FILE_NAME = ""
|
||||||
|
switch (process.env.NODE_ENV) {
|
||||||
|
case "production":
|
||||||
|
ENV_FILE_NAME = ".env.production"
|
||||||
|
break
|
||||||
|
case "staging":
|
||||||
|
ENV_FILE_NAME = ".env.staging"
|
||||||
|
break
|
||||||
|
case "test":
|
||||||
|
ENV_FILE_NAME = ".env.test"
|
||||||
|
break
|
||||||
|
case "development":
|
||||||
|
default:
|
||||||
|
ENV_FILE_NAME = ".env"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
dotenv.config({ path: process.cwd() + "/" + ENV_FILE_NAME })
|
||||||
|
} catch (e) {
|
||||||
|
reporter.warn(`Failed to load environment variables from ${ENV_FILE_NAME}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function build(args: BuildArgs) {
|
export default async function build(args: BuildArgs) {
|
||||||
const { path, backend, outDir } = mergeArgs(args)
|
const { deployment, outDir: outDirArg, backend, include, includeDist } = args
|
||||||
|
|
||||||
try {
|
let config: AdminBuildConfig = {}
|
||||||
validatePath(path)
|
|
||||||
} catch (err) {
|
if (deployment) {
|
||||||
reporter.panic(err)
|
config = {
|
||||||
|
build: {
|
||||||
|
outDir: outDirArg,
|
||||||
|
},
|
||||||
|
globals: {
|
||||||
|
backend: backend || process.env.MEDUSA_BACKEND_URL,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const { path, outDir } = loadConfig()
|
||||||
|
|
||||||
|
try {
|
||||||
|
validatePath(path)
|
||||||
|
} catch (err) {
|
||||||
|
reporter.panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
config = {
|
||||||
|
build: {
|
||||||
|
outDir: outDir,
|
||||||
|
},
|
||||||
|
globals: {
|
||||||
|
base: path,
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const time = Date.now()
|
const time = Date.now()
|
||||||
const spinner = ora().start(`Building Admin UI${EOL}`)
|
const spinner = ora().start(`Building Admin UI${EOL}`)
|
||||||
|
|
||||||
await buildAdmin({
|
await buildAdmin({
|
||||||
build: {
|
...config,
|
||||||
outDir: outDir,
|
|
||||||
},
|
|
||||||
globals: {
|
|
||||||
base: path,
|
|
||||||
backend: backend,
|
|
||||||
},
|
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
spinner.fail(`Failed to build Admin UI${EOL}`)
|
spinner.fail(`Failed to build Admin UI${EOL}`)
|
||||||
reporter.panic(err)
|
reporter.panic(err)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If we have specified files to include in the build, we copy them
|
||||||
|
* to the build directory.
|
||||||
|
*/
|
||||||
|
if (include && include.length > 0) {
|
||||||
|
const dist = outDirArg || resolve(process.cwd(), "build")
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (const filePath of include) {
|
||||||
|
await fse.copy(filePath, resolve(dist, includeDist, filePath))
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
reporter.panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
spinner.succeed(`Admin UI build - ${Date.now() - time}ms`)
|
spinner.succeed(`Admin UI build - ${Date.now() - time}ms`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const mergeArgs = (args: BuildArgs) => {
|
|
||||||
const { path, backend, outDir } = loadConfig()
|
|
||||||
|
|
||||||
return {
|
|
||||||
path: args.path || path,
|
|
||||||
backend: args.backend || backend,
|
|
||||||
outDir: args.outDir || outDir,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +1,47 @@
|
|||||||
import { Command } from "commander"
|
import { Command } from "commander"
|
||||||
import build from "./build"
|
import build from "./build"
|
||||||
|
import dev from "./dev"
|
||||||
|
import eject from "./eject"
|
||||||
|
|
||||||
export async function createCli(): Promise<Command> {
|
export async function createCli(): Promise<Command> {
|
||||||
const program = new Command()
|
const program = new Command()
|
||||||
|
|
||||||
const buildCommand = program.command("build")
|
const buildCommand = program.command("build")
|
||||||
buildCommand.description("Build the admin dashboard")
|
buildCommand.description("Build the admin dashboard")
|
||||||
|
|
||||||
|
buildCommand.option(
|
||||||
|
"--deployment",
|
||||||
|
"Build for deploying to and external host (e.g. Vercel)"
|
||||||
|
)
|
||||||
|
|
||||||
buildCommand.option("-o, --out-dir <path>", "Output directory")
|
buildCommand.option("-o, --out-dir <path>", "Output directory")
|
||||||
buildCommand.option("-b, --backend <url>", "Backend URL")
|
buildCommand.option("-b, --backend <url>", "Backend URL")
|
||||||
buildCommand.option("-p, --path <path>", "Base path")
|
buildCommand.option(
|
||||||
|
"-i, --include [paths...]]",
|
||||||
|
"Paths to files that should be included in the build"
|
||||||
|
)
|
||||||
|
buildCommand.option(
|
||||||
|
"-d, --include-dist <path>",
|
||||||
|
"Path to where the files specified in the include option should be placed. Relative to the root of the build directory."
|
||||||
|
)
|
||||||
|
|
||||||
buildCommand.action(build)
|
buildCommand.action(build)
|
||||||
|
|
||||||
|
const devCommand = program.command("dev")
|
||||||
|
devCommand.description("Start the admin dashboard in development mode")
|
||||||
|
devCommand.option("-p, --port <port>", "Port (default: 7001))")
|
||||||
|
devCommand.option(
|
||||||
|
"-b, --backend <url>",
|
||||||
|
"Backend URL (default http://localhost:9000)"
|
||||||
|
)
|
||||||
|
devCommand.action(dev)
|
||||||
|
|
||||||
|
const deployCommand = program.command("eject")
|
||||||
|
deployCommand.description(
|
||||||
|
"Eject the admin dashboard source code to a custom directory"
|
||||||
|
)
|
||||||
|
deployCommand.option("-o, --out-dir <path>", "Output directory")
|
||||||
|
deployCommand.action(eject)
|
||||||
|
|
||||||
return program
|
return program
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import type { AdminDevConfig } from "@medusajs/admin-ui"
|
||||||
|
import { dev as devAdmin } from "@medusajs/admin-ui"
|
||||||
|
|
||||||
|
export default async function dev(args: AdminDevConfig) {
|
||||||
|
await devAdmin(args)
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
import * as fse from "fs-extra"
|
||||||
|
import path from "path"
|
||||||
|
import dedent from "ts-dedent"
|
||||||
|
|
||||||
|
type EjectParams = {
|
||||||
|
outDir?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_DESTINATION = "medusa-admin-ui"
|
||||||
|
|
||||||
|
export default async function eject({
|
||||||
|
outDir = DEFAULT_DESTINATION,
|
||||||
|
}: EjectParams) {
|
||||||
|
const projectPath = require.resolve("@medusajs/admin-ui")
|
||||||
|
const uiPath = path.join(projectPath, "..", "..", "ui")
|
||||||
|
|
||||||
|
const packageJsonPath = path.join(projectPath, "..", "..", "package.json")
|
||||||
|
const pkg = await fse.readJSON(packageJsonPath)
|
||||||
|
|
||||||
|
const fieldsToRemove = ["exports", "types", "files", "main", "packageManager"]
|
||||||
|
fieldsToRemove.forEach((field) => delete pkg[field])
|
||||||
|
|
||||||
|
pkg.type = "module"
|
||||||
|
|
||||||
|
const dependenciesToMove = [
|
||||||
|
"tailwindcss",
|
||||||
|
"autoprefixer",
|
||||||
|
"postcss",
|
||||||
|
"tailwindcss-radix",
|
||||||
|
"@tailwindcss/forms",
|
||||||
|
"vite",
|
||||||
|
"@vitejs/plugin-react",
|
||||||
|
]
|
||||||
|
|
||||||
|
// Get dependencies in array from pkg.dependencies and move them to pkg.devDependencies
|
||||||
|
const dependencies = Object.keys(pkg.dependencies).filter((dep) =>
|
||||||
|
dependenciesToMove.includes(dep)
|
||||||
|
)
|
||||||
|
|
||||||
|
dependencies.forEach((dep) => {
|
||||||
|
pkg.devDependencies[dep] = pkg.dependencies[dep]
|
||||||
|
delete pkg.dependencies[dep]
|
||||||
|
})
|
||||||
|
|
||||||
|
pkg.scripts = {
|
||||||
|
build: "vite build",
|
||||||
|
dev: "vite --port 7001",
|
||||||
|
preview: "vite preview",
|
||||||
|
}
|
||||||
|
|
||||||
|
const viteConfig = dedent`
|
||||||
|
import { defineConfig } from "vite"
|
||||||
|
import dns from "dns"
|
||||||
|
import react from "@vitejs/plugin-react"
|
||||||
|
|
||||||
|
// Resolve localhost for Node v16 and older.
|
||||||
|
// @see https://vitejs.dev/config/server-options.html#server-host.
|
||||||
|
dns.setDefaultResultOrder("verbatim")
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
define: {
|
||||||
|
__BASE__: JSON.stringify("/"),
|
||||||
|
__MEDUSA_BACKEND_URL__: JSON.stringify("http://localhost:9000"),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
`
|
||||||
|
|
||||||
|
// Create new tailwind.config.js file based on the current one
|
||||||
|
const tailwindConfig = await fse.readFile(
|
||||||
|
path.join(uiPath, "tailwind.config.js"),
|
||||||
|
"utf-8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Overwrite content field of module.exports in tailwind.config.js
|
||||||
|
let newTailwindConfig = tailwindConfig.replace(
|
||||||
|
/content:\s*\[[\s\S]*?\]/,
|
||||||
|
`content: ["src/**/*.{js,ts,jsx,tsx}", "./index.html"]`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Remove require of "path" in tailwind.config.js
|
||||||
|
newTailwindConfig = newTailwindConfig.replace(
|
||||||
|
/const path = require\("path"\)/,
|
||||||
|
""
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create a new postcss.config.js file
|
||||||
|
const postcssConfig = dedent`
|
||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
"tailwindcss/nesting": {},
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const tmpPath = path.join(process.cwd(), outDir)
|
||||||
|
|
||||||
|
await fse.copy(uiPath, tmpPath)
|
||||||
|
await fse.remove(path.join(tmpPath, "tailwind.config.js"))
|
||||||
|
await fse.remove(path.join(tmpPath, "postcss.config.js"))
|
||||||
|
await fse.writeJSON(path.join(tmpPath, "package.json"), pkg)
|
||||||
|
await fse.writeFile(path.join(tmpPath, "vite.config.ts"), viteConfig)
|
||||||
|
await fse.writeFile(
|
||||||
|
path.join(tmpPath, "tailwind.config.cjs"),
|
||||||
|
newTailwindConfig
|
||||||
|
)
|
||||||
|
await fse.writeFile(path.join(tmpPath, "postcss.config.cjs"), postcssConfig)
|
||||||
|
}
|
||||||
@@ -6,7 +6,14 @@ import { resolve } from "path"
|
|||||||
import { loadConfig, reporter, validatePath } from "../utils"
|
import { loadConfig, reporter, validatePath } from "../utils"
|
||||||
|
|
||||||
export default async function setupAdmin() {
|
export default async function setupAdmin() {
|
||||||
const { path, backend, outDir } = loadConfig()
|
const { path, outDir, serve, autoRebuild } = loadConfig()
|
||||||
|
|
||||||
|
// If the user has not specified that the admin UI should be served,
|
||||||
|
// we should not build it. Furthermore, if the user has not specified that they want
|
||||||
|
// the admin UI to be rebuilt on changes, we should not build it here.
|
||||||
|
if (!serve || !autoRebuild) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
validatePath(path)
|
validatePath(path)
|
||||||
@@ -17,11 +24,13 @@ export default async function setupAdmin() {
|
|||||||
let dir: string
|
let dir: string
|
||||||
let shouldBuild = false
|
let shouldBuild = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If no outDir is provided we default to "build".
|
||||||
|
*/
|
||||||
if (outDir) {
|
if (outDir) {
|
||||||
dir = resolve(process.cwd(), outDir)
|
dir = resolve(process.cwd(), outDir)
|
||||||
} else {
|
} else {
|
||||||
const uiPath = require.resolve("@medusajs/admin-ui")
|
dir = resolve(process.cwd(), "build")
|
||||||
dir = resolve(uiPath, "..", "..", "build")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -34,11 +43,16 @@ export default async function setupAdmin() {
|
|||||||
|
|
||||||
const buildOptions = {
|
const buildOptions = {
|
||||||
build: {
|
build: {
|
||||||
outDir: outDir,
|
outDir,
|
||||||
},
|
},
|
||||||
globals: {
|
globals: {
|
||||||
base: path,
|
base: path,
|
||||||
backend: backend,
|
/**
|
||||||
|
* We only build the admin UI as part of the Medusa startup process if
|
||||||
|
* the user has specified that they want to serve the admin UI. When this
|
||||||
|
* is the case, we should always set the backend to `undefined`.
|
||||||
|
*/
|
||||||
|
backend: undefined,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,13 +81,7 @@ export default async function setupAdmin() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
await build({
|
await build({
|
||||||
build: {
|
...buildOptions,
|
||||||
outDir: outDir,
|
|
||||||
},
|
|
||||||
globals: {
|
|
||||||
base: path,
|
|
||||||
backend: backend,
|
|
||||||
},
|
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
spinner.fail(`Failed to build Admin UI${EOL}`)
|
spinner.fail(`Failed to build Admin UI${EOL}`)
|
||||||
reporter.panic(err)
|
reporter.panic(err)
|
||||||
|
|||||||
@@ -3,27 +3,30 @@ export type PluginOptions = {
|
|||||||
* Determines whether the admin dashboard should be served.
|
* Determines whether the admin dashboard should be served.
|
||||||
*/
|
*/
|
||||||
serve?: boolean
|
serve?: boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The path to the admin dashboard. Should not be either prefixed or suffixed with a slash.
|
* The path to the admin dashboard. Should not be prefixed or suffixed with a slash.
|
||||||
* The chosen path cannot be one of the reserved paths: "admin", "store".
|
* The chosen path cannot be one of the reserved paths: "admin", "store".
|
||||||
* @default "app"
|
* @default "app"
|
||||||
*/
|
*/
|
||||||
path?: string
|
path?: string
|
||||||
/**
|
|
||||||
* Backend to use for the admin dashboard. This should only be used if you
|
|
||||||
* intend on hosting the dashboard separately from your Medusa server.
|
|
||||||
* @default undefined
|
|
||||||
*/
|
|
||||||
backend?: string
|
|
||||||
/**
|
/**
|
||||||
* The directory to output the build to. By default the plugin will build
|
* The directory to output the build to. By default the plugin will build
|
||||||
* the dashboard to the `build` directory of the `@medusajs/admin-ui` package.
|
* the dashboard to the `build` directory in `/node_modules/@medusajs/admin-ui`.
|
||||||
* If you intend on hosting the dashboard separately from your Medusa server,
|
|
||||||
* you should use this option to specify a custom build directory, that you can
|
|
||||||
* deploy to your host of choice.
|
|
||||||
* @default undefined
|
* @default undefined
|
||||||
*/
|
*/
|
||||||
outDir?: string
|
outDir?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Re-build the admin automatically when options have changed since the last server start.
|
||||||
|
* Be aware that building the dashboard is a memory intensive process. Therefore, this option should
|
||||||
|
* only be used in production if your server has the memory required to support it.
|
||||||
|
*
|
||||||
|
* Only used if `serve` is `true`.
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
autoRebuild?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
type PluginObject = {
|
type PluginObject = {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export const loadConfig = () => {
|
|||||||
|
|
||||||
let defaultConfig: PluginOptions = {
|
let defaultConfig: PluginOptions = {
|
||||||
serve: true,
|
serve: true,
|
||||||
|
autoRebuild: false,
|
||||||
path: "app",
|
path: "app",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,8 +23,8 @@ export const loadConfig = () => {
|
|||||||
const { options } = plugin as { options: PluginOptions }
|
const { options } = plugin as { options: PluginOptions }
|
||||||
defaultConfig = {
|
defaultConfig = {
|
||||||
serve: options.serve ?? defaultConfig.serve,
|
serve: options.serve ?? defaultConfig.serve,
|
||||||
|
autoRebuild: options.autoRebuild ?? defaultConfig.autoRebuild,
|
||||||
path: options.path ?? defaultConfig.path,
|
path: options.path ?? defaultConfig.path,
|
||||||
backend: options.backend ?? defaultConfig.backend,
|
|
||||||
outDir: options.outDir ?? defaultConfig.outDir,
|
outDir: options.outDir ?? defaultConfig.outDir,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
export const validatePath = (path: string) => {
|
export const validatePath = (path?: string) => {
|
||||||
|
if (!path) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (path.startsWith("/")) {
|
if (path.startsWith("/")) {
|
||||||
throw new Error(`Path cannot start with a slash.`)
|
throw new Error(`Path cannot start with a slash.`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5680,11 +5680,13 @@ __metadata:
|
|||||||
"@medusajs/admin-ui": "*"
|
"@medusajs/admin-ui": "*"
|
||||||
"@types/express": ^4.17.13
|
"@types/express": ^4.17.13
|
||||||
commander: ^10.0.0
|
commander: ^10.0.0
|
||||||
|
dotenv: ^16.0.3
|
||||||
express: ^4.17.1
|
express: ^4.17.1
|
||||||
fs-extra: ^11.1.0
|
fs-extra: ^11.1.0
|
||||||
medusa-core-utils: "*"
|
medusa-core-utils: "*"
|
||||||
ora: 5.4.0
|
ora: 5.4.0
|
||||||
picocolors: ^1.0.0
|
picocolors: ^1.0.0
|
||||||
|
ts-dedent: ^2.2.0
|
||||||
typescript: ^4.9.3
|
typescript: ^4.9.3
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
"@medusajs/medusa": "*"
|
"@medusajs/medusa": "*"
|
||||||
@@ -18025,7 +18027,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"dotenv@npm:^16.0.0":
|
"dotenv@npm:^16.0.0, dotenv@npm:^16.0.3":
|
||||||
version: 16.0.3
|
version: 16.0.3
|
||||||
resolution: "dotenv@npm:16.0.3"
|
resolution: "dotenv@npm:16.0.3"
|
||||||
checksum: 109457ac5f9e930ca8066ea33887b6f839ab24d647a7a8b49ddcd1f32662e2c35591c5e5b9819063e430148a664d0927f0cbe60cf9575d89bc524f47ff7e78f0
|
checksum: 109457ac5f9e930ca8066ea33887b6f839ab24d647a7a8b49ddcd1f32662e2c35591c5e5b9819063e430148a664d0927f0cbe60cf9575d89bc524f47ff7e78f0
|
||||||
@@ -38219,7 +38221,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"ts-dedent@npm:^2.0.0":
|
"ts-dedent@npm:^2.0.0, ts-dedent@npm:^2.2.0":
|
||||||
version: 2.2.0
|
version: 2.2.0
|
||||||
resolution: "ts-dedent@npm:2.2.0"
|
resolution: "ts-dedent@npm:2.2.0"
|
||||||
checksum: 175adea838468cc2ff7d5e97f970dcb798bbcb623f29c6088cb21aa2880d207c5784be81ab1741f56b9ac37840cbaba0c0d79f7f8b67ffe61c02634cafa5c303
|
checksum: 175adea838468cc2ff7d5e97f970dcb798bbcb623f29c6088cb21aa2880d207c5784be81ab1741f56b9ac37840cbaba0c0d79f7f8b67ffe61c02634cafa5c303
|
||||||
|
|||||||
Reference in new issue
Block a user