Browse Source

feat(server): Create AdminUiPlugin

Relates to #50
Michael Bromley 7 years ago
parent
commit
c6fbcb2f34

+ 40 - 16
server/build/gulpfile.ts

@@ -1,23 +1,47 @@
-import gulp from 'gulp';
+import { exec } from 'child_process';
+import { dest, parallel, series, src } from 'gulp';
 import path from 'path';
 
-gulp.task('copy-schemas', () => {
-    return gulp.src(['../src/**/*.graphql']).pipe(gulp.dest('../dist/server/src'));
-});
+// tslint:disable:no-console
 
-gulp.task('copy-email-templates', () => {
-    return gulp.src(['../src/email/templates/**/*']).pipe(gulp.dest('../dist/cli/assets/email-templates'));
-});
+function copySchemas() {
+    return src(['../src/**/*.graphql']).pipe(dest('../dist/server/src'));
+}
 
-gulp.task('copy-cli-assets', () => {
-    return gulp.src(['../cli/assets/**/*']).pipe(gulp.dest('../dist/cli/assets'));
-});
+function copyEmailTemplates() {
+    return src(['../src/email/templates/**/*']).pipe(dest('../dist/cli/assets/email-templates'));
+}
 
-gulp.task('copy-cli-images', () => {
-    return gulp.src(['../mock-data/assets/**/*']).pipe(gulp.dest('../dist/cli/assets/images'));
-});
+function copyCliAssets() {
+    return src(['../cli/assets/**/*']).pipe(dest('../dist/cli/assets'));
+}
 
-gulp.task(
-    'default',
-    gulp.parallel(['copy-schemas', 'copy-email-templates', 'copy-cli-assets', 'copy-cli-images']),
+function copyCliImages() {
+    return src(['../mock-data/assets/**/*']).pipe(dest('../dist/cli/assets/images'));
+}
+
+function buildAdminUi() {
+    return exec(
+        'yarn build --prod=true',
+        {
+            cwd: path.join(__dirname, '../../admin-ui'),
+        },
+        error => console.log(error),
+    );
+}
+
+function copyAdminUi() {
+    return src(['../../admin-ui/dist/vendure-admin/**/*']).pipe(dest('../dist/admin-ui'));
+}
+
+function buildAndCopyAdminUi() {
+    return src(['../mock-data/assets/**/*']).pipe(dest('../dist/cli/assets/images'));
+}
+
+export const build = parallel(
+    copySchemas,
+    copyEmailTemplates,
+    copyCliAssets,
+    copyCliImages,
+    series(buildAdminUi, copyAdminUi),
 );

+ 4 - 0
server/dev-config.ts

@@ -7,6 +7,7 @@ import { OrderProcessOptions, VendureConfig } from './src/config/vendure-config'
 import { defaultEmailTypes } from './src/email/default-email-types';
 import { HandlebarsMjmlGenerator } from './src/email/handlebars-mjml-generator';
 import { DefaultAssetServerPlugin } from './src/plugin';
+import { AdminUiPlugin } from './src/plugin/admin-ui-plugin/admin-ui-plugin';
 import { DefaultSearchPlugin } from './src/plugin/default-search-plugin/default-search-plugin';
 
 /**
@@ -57,5 +58,8 @@ export const devConfig: VendureConfig = {
             port: 4000,
         }),
         new DefaultSearchPlugin(),
+        new AdminUiPlugin({
+            port: 3001,
+        }),
     ],
 };

+ 2 - 1
server/package.json

@@ -13,7 +13,7 @@
     "test:cov": "jest --coverage",
     "test:e2e": "jest --config ./e2e/config/jest-e2e.json --runInBand",
     "test:e2e:watch": "jest --config ./e2e/config/jest-e2e.json --watch --runInBand",
-    "build": "rimraf dist && tsc -p ./build/tsconfig.build.json && tsc -p ./build/tsconfig.cli.json && gulp -f ./build/gulpfile.ts",
+    "build": "rimraf dist && tsc -p ./build/tsconfig.build.json && tsc -p ./build/tsconfig.cli.json && gulp -f ./build/gulpfile.ts build",
     "generate-email-preview": "node -r ts-node/register src/email/preview/generate-email-preview.ts"
   },
   "main": "dist/server/src/index.js",
@@ -72,6 +72,7 @@
     "@types/fs-extra": "^5.0.4",
     "@types/gulp": "^4.0.5",
     "@types/handlebars": "^4.0.40",
+    "@types/http-proxy-middleware": "^0.19.2",
     "@types/jest": "^23.3.12",
     "@types/mime-types": "^2.1.0",
     "@types/nanoid": "^1.2.0",

+ 54 - 0
server/src/plugin/admin-ui-plugin/admin-ui-plugin.ts

@@ -0,0 +1,54 @@
+import express from 'express';
+import fs from 'fs-extra';
+import path from 'path';
+
+import { VendureConfig } from '../../config/vendure-config';
+import { InjectorFn, VendurePlugin } from '../../config/vendure-plugin/vendure-plugin';
+import { createProxyHandler } from '../plugin-utils';
+
+export interface AdminUiOptions {
+    hostname?: string;
+    port: number;
+}
+
+/**
+ * This plugin starts a static server for the Admin UI app, and proxies it via the `/admin/` path
+ * of the main Vendure server.
+ */
+export class AdminUiPlugin implements VendurePlugin {
+    constructor(private options: AdminUiOptions) {}
+
+    configure(config: Required<VendureConfig>): Required<VendureConfig> {
+        const route = 'admin';
+        config.middleware.push({
+            handler: createProxyHandler({ ...this.options, route }, !config.silent),
+            route,
+        });
+        return config;
+    }
+
+    onBootstrap(inject: InjectorFn): void | Promise<void> {
+        const adminUiPath = this.getAdminUiPath();
+        const assetServer = express();
+        assetServer.use(express.static(adminUiPath));
+        assetServer.use((req, res) => {
+            res.sendFile(path.join(adminUiPath, 'index.html'));
+        });
+        assetServer.listen(this.options.port);
+    }
+
+    private getAdminUiPath(): string {
+        // attempt to read the index.html file from the Vendure dist bundle (as when installed
+        // in an end-user project)
+        const prodPath = path.join(__dirname, '../../../../admin-ui');
+        if (fs.existsSync(path.join(prodPath, 'index.html'))) {
+            return prodPath;
+        }
+        // attempt to read from the built admin-ui in the /server/dist/ folder when developing
+        const devPath = path.join(__dirname, '../../../dist/admin-ui');
+        if (fs.existsSync(path.join(devPath, 'index.html'))) {
+            return devPath;
+        }
+        throw new Error(`AdminUiPlugin: admin-ui app not found`);
+    }
+}

+ 3 - 21
server/src/plugin/default-asset-server-plugin/default-asset-server-plugin.ts

@@ -1,11 +1,10 @@
-import express from 'express';
-import { NextFunction, Request, Response } from 'express';
-import proxy from 'http-proxy-middleware';
+import express, { NextFunction, Request, Response } from 'express';
 import path from 'path';
 
 import { AssetStorageStrategy } from '../../config/asset-storage-strategy/asset-storage-strategy';
 import { VendureConfig } from '../../config/vendure-config';
 import { InjectorFn, VendurePlugin } from '../../config/vendure-plugin/vendure-plugin';
+import { createProxyHandler } from '../plugin-utils';
 
 import { DefaultAssetPreviewStrategy } from './default-asset-preview-strategy';
 import { DefaultAssetStorageStrategy } from './default-asset-storage-strategy';
@@ -67,7 +66,7 @@ export class DefaultAssetServerPlugin implements VendurePlugin {
         });
         config.assetOptions.assetStorageStrategy = this.assetStorage;
         config.middleware.push({
-            handler: this.createProxyHandler(config.hostname, !config.silent),
+            handler: createProxyHandler(this.options, !config.silent),
             route: this.options.route,
         });
         return config;
@@ -138,23 +137,6 @@ export class DefaultAssetServerPlugin implements VendurePlugin {
         return req.path;
     }
 
-    /**
-     * Configures the proxy middleware which will be passed to the main Vendure server. This
-     * will proxy all asset requests to the dedicated asset server.
-     */
-    private createProxyHandler(serverHostname: string, logging: boolean) {
-        const route = this.options.route.charAt(0) === '/' ? this.options.route : '/' + this.options.route;
-        const proxyHostname = this.options.hostname ? this.options.hostname : serverHostname || 'localhost';
-        return proxy({
-            // TODO: how do we detect https?
-            target: `http://${proxyHostname}:${this.options.port}`,
-            pathRewrite: {
-                [`^${route}`]: '/',
-            },
-            logLevel: logging ? 'info' : 'silent',
-        });
-    }
-
     private addSuffix(fileName: string, suffix: string): string {
         const ext = path.extname(fileName);
         const baseName = path.basename(fileName, ext);

+ 24 - 0
server/src/plugin/plugin-utils.ts

@@ -0,0 +1,24 @@
+import proxy from 'http-proxy-middleware';
+
+export interface ProxyOptions {
+    route: string;
+    port: number;
+    hostname?: string;
+}
+
+/**
+ * Configures the proxy middleware which will be passed to the main Vendure server. This
+ * will proxy all asset requests to the dedicated asset server.
+ */
+export function createProxyHandler(options: ProxyOptions, logging: boolean) {
+    const route = options.route.charAt(0) === '/' ? options.route : '/' + options.route;
+    const proxyHostname = options.hostname || 'localhost';
+    return proxy({
+        // TODO: how do we detect https?
+        target: `http://${proxyHostname}:${options.port}`,
+        pathRewrite: {
+            [`^${route}`]: `/`,
+        },
+        logLevel: logging ? 'info' : 'silent',
+    });
+}

+ 17 - 0
server/yarn.lock

@@ -274,6 +274,23 @@
   resolved "https://registry.yarnpkg.com/@types/handlebars/-/handlebars-4.0.40.tgz#b714e13d296a75bff3f199316d1311933ca79ffd"
   integrity sha512-sGWNtsjNrLOdKha2RV1UeF8+UbQnPSG7qbe5wwbni0mw4h2gHXyPFUMOC+xwGirIiiydM/HSqjDO4rk6NFB18w==
 
+"@types/http-proxy-middleware@^0.19.2":
+  version "0.19.2"
+  resolved "https://registry.yarnpkg.com/@types/http-proxy-middleware/-/http-proxy-middleware-0.19.2.tgz#1c44b96487cb2f333102b762c56a8f02241e85bd"
+  integrity sha512-aXcAs2VEaiHwlFlEqMJ+sNSFCO+wuWXcvdBk5Un7f0tUv1eTIIAmkd4S5D/Yi5JI0xofPpm9h3017TngbrLh7A==
+  dependencies:
+    "@types/connect" "*"
+    "@types/http-proxy" "*"
+    "@types/node" "*"
+
+"@types/http-proxy@*":
+  version "1.16.2"
+  resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.16.2.tgz#16cb373b52fff2aa2f389d23d940ed4a642349e5"
+  integrity sha512-GgqePmC3rlsn1nv+kx5OviPuUBU2omhnlXOaJSXFgOdsTcScNFap+OaCb2ip9Bm4m5L8EOehgT5d9M4uNB90zg==
+  dependencies:
+    "@types/events" "*"
+    "@types/node" "*"
+
 "@types/jest@^23.3.12":
   version "23.3.12"
   resolved "https://registry.yarnpkg.com/@types/jest/-/jest-23.3.12.tgz#7e0ced251fa94c3bc2d1023d4b84b2992fa06376"