Skip to content

Commit

Permalink
Merge branch 'rework-admin-ui'
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelbromley committed Mar 12, 2020
2 parents a574fa1 + 2b9e4c4 commit 50df148
Show file tree
Hide file tree
Showing 619 changed files with 3,254 additions and 1,916 deletions.
21 changes: 19 additions & 2 deletions packages/admin-ui-plugin/build.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,23 @@
/* tslint:disable:no-console */
import { compileAdminUiApp } from '@vendure/admin-ui/compiler/compile';
import { execSync, spawn } from 'child_process';
import fs from 'fs-extra';
import path from 'path';

const compiledUiDir = path.join(__dirname, 'lib/admin-ui');
console.log('Building admin-ui from source...');
compileAdminUiApp(path.join(__dirname, 'lib/admin-ui'), []);

fs.remove(compiledUiDir);

const buildProcess = spawn('yarn', ['run', 'build:app'], {
cwd: path.join(__dirname, '../admin-ui'),
shell: true,
stdio: 'inherit',
});

buildProcess.on('close', code => {
if (code === 0) {
fs.copySync(path.join(__dirname, '../admin-ui/dist'), compiledUiDir);
} else {
console.log('Could not build!');
}
});
1 change: 0 additions & 1 deletion packages/admin-ui-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
"typescript": "~3.5.3"
},
"dependencies": {
"@vendure/admin-ui": "^0.9.0",
"fs-extra": "^8.0.1"
}
}
2 changes: 1 addition & 1 deletion packages/admin-ui-plugin/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import path from 'path';

export const UI_PATH = path.join(__dirname, '../admin-ui');
export const DEFAULT_APP_PATH = path.join(__dirname, '../admin-ui');
export const loggerCtx = 'AdminUiPlugin';
168 changes: 117 additions & 51 deletions packages/admin-ui-plugin/src/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { Watcher } from '@vendure/admin-ui/compiler/watch';
import { DEFAULT_AUTH_TOKEN_HEADER_KEY } from '@vendure/common/lib/shared-constants';
import { AdminUiConfig, AdminUiExtension, Type } from '@vendure/common/lib/shared-types';
import {
AdminUiAppConfig,
AdminUiAppDevModeConfig,
AdminUiConfig,
Type,
} from '@vendure/common/lib/shared-types';
import {
AuthOptions,
ConfigService,
Expand All @@ -17,8 +21,7 @@ import fs from 'fs-extra';
import { Server } from 'http';
import path from 'path';

import { UI_PATH } from './constants';
import { UiAppCompiler } from './ui-app-compiler.service';
import { DEFAULT_APP_PATH, loggerCtx } from './constants';

/**
* @description
Expand All @@ -27,6 +30,11 @@ import { UiAppCompiler } from './ui-app-compiler.service';
* @docsCategory AdminUiPlugin
*/
export interface AdminUiOptions {
/**
* @description
* The port on which the server will listen. If not
*/
port: number;
/**
* @description
* The hostname of the server serving the static admin ui files.
Expand All @@ -36,9 +44,11 @@ export interface AdminUiOptions {
hostname?: string;
/**
* @description
* The port on which the server will listen.
* By default, the AdminUiPlugin comes bundles with a pre-built version of the
* Admin UI. This option can be used to override this default build with a different
* version, e.g. one pre-compiled with one or more ui extensions.
*/
port: number;
app?: AdminUiAppConfig | AdminUiAppDevModeConfig;
/**
* @description
* The hostname of the Vendure server which the admin ui will be making API calls
Expand All @@ -57,22 +67,6 @@ export interface AdminUiOptions {
* @default 'auto'
*/
apiPort?: number | 'auto';
/**
* @description
* An optional array of objects which configure extension Angular modules
* to be compiled into and made available by the AdminUi application.
*/
extensions?: AdminUiExtension[];
/**
* @description
* Set to `true` in order to run the Admin UI in development mode (using the Angular CLI
* [ng serve](https://angular.io/cli/serve) command). When in watch mode, any changes to
* UI extension files will be watched and trigger a rebuild of the Admin UI with live
* reloading.
*
* @default false
*/
watch?: boolean;
}

/**
Expand Down Expand Up @@ -106,15 +100,14 @@ export interface AdminUiOptions {
*/
@VendurePlugin({
imports: [PluginCommonModule],
providers: [UiAppCompiler],
providers: [],
configuration: config => AdminUiPlugin.configure(config),
})
export class AdminUiPlugin implements OnVendureBootstrap, OnVendureClose {
private static options: AdminUiOptions;
private server: Server;
private watcher: Watcher | undefined;

constructor(private configService: ConfigService, private appCompiler: UiAppCompiler) {}
constructor(private configService: ConfigService) {}

/**
* @description
Expand All @@ -128,19 +121,29 @@ export class AdminUiPlugin implements OnVendureBootstrap, OnVendureClose {
/** @internal */
static async configure(config: RuntimeVendureConfig): Promise<RuntimeVendureConfig> {
const route = 'admin';
const { app } = this.options;
const appWatchMode = this.isDevModeApp(app);
let port: number;
if (this.isDevModeApp(app)) {
port = app.port;
} else {
port = this.options.port;
}
config.middleware.push({
handler: createProxyHandler({
...this.options,
hostname: this.options.hostname,
port,
route: 'admin',
label: 'Admin UI',
basePath: this.options.watch ? 'admin' : undefined,
basePath: appWatchMode ? 'admin' : undefined,
}),
route,
});
if (this.options.watch) {
if (this.isDevModeApp(app)) {
config.middleware.push({
handler: createProxyHandler({
...this.options,
hostname: this.options.hostname,
port,
route: 'sockjs-node',
label: 'Admin UI live reload',
basePath: 'sockjs-node',
Expand All @@ -154,36 +157,59 @@ export class AdminUiPlugin implements OnVendureBootstrap, OnVendureClose {
/** @internal */
async onVendureBootstrap() {
const { adminApiPath, authOptions } = this.configService;
const { apiHost, apiPort, extensions, watch, port } = AdminUiPlugin.options;
let adminUiConfigPath: string;
const { apiHost, apiPort, port, app } = AdminUiPlugin.options;
const adminUiAppPath = AdminUiPlugin.isDevModeApp(app)
? path.join(app.sourcePath, 'src')
: (app && app.path) || DEFAULT_APP_PATH;
const adminUiConfigPath = path.join(adminUiAppPath, 'vendure-ui-config.json');
const overwriteConfig = () =>
this.overwriteAdminUiConfig({
host: apiHost || 'auto',
port: apiPort || 'auto',
adminApiPath,
authOptions,
adminUiConfigPath,
});

if (watch) {
this.watcher = this.appCompiler.watchAdminUiApp(extensions, port);
adminUiConfigPath = path.join(__dirname, '../../../admin-ui/src', 'vendure-ui-config.json');
} else {
const adminUiPath = await this.appCompiler.compileAdminUiApp(extensions);
if (!AdminUiPlugin.isDevModeApp(app)) {
// If not in dev mode, start a static server for the compiled app
const adminUiServer = express();
adminUiServer.use(express.static(UI_PATH));
adminUiServer.use(express.static(adminUiAppPath));
adminUiServer.use((req, res) => {
res.sendFile(path.join(UI_PATH, 'index.html'));
res.sendFile(path.join(adminUiAppPath, 'index.html'));
});
this.server = adminUiServer.listen(AdminUiPlugin.options.port);
adminUiConfigPath = path.join(UI_PATH, 'vendure-ui-config.json');
if (app && typeof app.compile === 'function') {
Logger.info(`Compiling Admin UI app in production mode...`, loggerCtx);
app.compile()
.then(overwriteConfig)
.then(
() => {
Logger.info(`Admin UI successfully compiled`, loggerCtx);
},
(err: any) => {
Logger.error(`Failed to compile: ${err}`, loggerCtx, err.stack);
},
);
} else {
await overwriteConfig();
}
} else {
Logger.info(`Compiling Admin UI app in development mode`, loggerCtx);
app.compile().then(
() => {
Logger.info(`Admin UI compiling and watching for changes...`, loggerCtx);
},
(err: any) => {
Logger.error(`Failed to compile: ${err}`, loggerCtx, err.stack);
},
);
await overwriteConfig();
}
await this.overwriteAdminUiConfig({
host: apiHost || 'auto',
port: apiPort || 'auto',
adminApiPath,
authOptions,
adminUiConfigPath,
});
}

/** @internal */
async onVendureClose(): Promise<void> {
if (this.watcher) {
this.watcher.close();
}
if (this.server) {
await new Promise(resolve => this.server.close(() => resolve()));
}
Expand All @@ -201,10 +227,40 @@ export class AdminUiPlugin implements OnVendureBootstrap, OnVendureClose {
adminUiConfigPath: string;
}) {
const { host, port, adminApiPath, authOptions, adminUiConfigPath } = options;
const adminUiConfig = await fs.readFile(adminUiConfigPath, 'utf-8');

/**
* It might be that the ui-devkit compiler has not yet copied the config
* file to the expected location (perticularly when running in watch mode),
* so polling is used to check multiple times with a delay.
*/
async function pollForConfigFile() {
let configFileContent: string;
const maxRetries = 5;
const retryDelay = 200;
let attempts = 0;
return new Promise<string>(async function checkForFile(resolve, reject) {
if (attempts >= maxRetries) {
reject();
}
try {
Logger.verbose(`Checking for config file: ${adminUiConfigPath}`, loggerCtx);
configFileContent = await fs.readFile(adminUiConfigPath, 'utf-8');
resolve(configFileContent);
} catch (e) {
attempts++;
Logger.verbose(
`Unable to locate config file: ${adminUiConfigPath} (attempt ${attempts})`,
loggerCtx,
);
setTimeout(pollForConfigFile, retryDelay, resolve, reject);
}
});
}

const content = await pollForConfigFile();
let config: AdminUiConfig;
try {
config = JSON.parse(adminUiConfig);
config = JSON.parse(content);
} catch (e) {
throw new Error('[AdminUiPlugin] Could not parse vendure-ui-config.json file:\n' + e.message);
}
Expand All @@ -214,5 +270,15 @@ export class AdminUiPlugin implements OnVendureBootstrap, OnVendureClose {
config.tokenMethod = authOptions.tokenMethod || 'cookie';
config.authTokenHeaderKey = authOptions.authTokenHeaderKey || DEFAULT_AUTH_TOKEN_HEADER_KEY;
await fs.writeFile(adminUiConfigPath, JSON.stringify(config, null, 2));
Logger.verbose(`Applied configuration to vendure-ui-config.json file`, loggerCtx);
}

private static isDevModeApp(
app?: AdminUiAppConfig | AdminUiAppDevModeConfig,
): app is AdminUiAppDevModeConfig {
if (!app) {
return false;
}
return !!(app as AdminUiAppDevModeConfig).sourcePath;
}
}
Loading

0 comments on commit 50df148

Please sign in to comment.