Parcourir la source

feat(admin-ui): Improved ui extension development API & architecture

BREAKING CHANGE: This release introduces a re-architected solution for handling extensions to the Admin UI. *If you do not use the ui extensions feature, you will not need to change anything*. For those already using ui extensions, these are the changes:

* The `@vendure/admin-ui-plugin` now contains only the default admin ui app.
* To create extensions, you will need to install `@vendure/ui-devkit`, which exposes a `compileUiExtensions()` function.
* Here is an example of how the config differs:
  ```ts
    // before
    AdminUiPlugin.init({
        port: 3002,
        extensions: [
            ReviewsPlugin.uiExtensions,
            RewardsPlugin.uiExtensions,
        ],
        watch: true,
    }),
  ```
  ```ts
    // after
  import { compileUiExtensions } from '@vendure/ui-devkit/compiler';

  // ...

    AdminUiPlugin.init({
        port: 3002,
        app: compileUiExtensions({
            // The source files of the admin ui, extended with your extensions,
            // will be output and compiled from this location
            outputPath: path.join(__dirname, '../admin-ui'),
            extensions: [
                ReviewsPlugin.uiExtensions,
                RewardsPlugin.uiExtensions,
            ],
            watch: true,
        }),
    }),
  ```
* For lazy-loaded extension modules, you must now specify a `route` property. This allows us to lazy-load each extension individually, whereas previously _all_ extensions were bundled into a single lazy-loaded chunk.
  ```diff
  export class ReviewsPlugin {
      static uiExtensions: AdminUiExtension = {
          extensionPath: path.join(__dirname, 'ui'),
          id: 'reviews-plugin',
          ngModules: [{
              type: 'lazy',
  +           route: 'product-reviews',
              ngModuleFileName: 'reviews-ui-lazy.module.ts',
              ngModuleName: 'ReviewsUiLazyModule',
          }],
      };
  }

  // in the route config of the lazy-loaded module
  {
  -   path: 'product-reviews',
  +   path: '',
  +   pathMatch: 'full',
      component: AllProductReviewsListComponent,
  },
  ```
* The `CustomFieldControl` interface changed slightly:
  ```diff
  import {
  - CustomFieldConfig,
  + CustomFieldConfigType,
    CustomFieldControl,
  } from '@vendure/admin-ui/core';

  @Component({
      // ...
  })
  export class ReviewCountComponent implements CustomFieldControl  {
  -   customFieldConfig: CustomFieldConfig;
  +   customFieldConfig: CustomFieldConfigType;
      formControl: FormControl;
      // ...
  }
  ```
Michael Bromley il y a 5 ans
Parent
commit
fe72c41e7c

+ 0 - 1
packages/admin-ui-plugin/package.json

@@ -26,7 +26,6 @@
     "typescript": "~3.5.3"
   },
   "dependencies": {
-    "@vendure/admin-ui": "^0.9.0",
     "fs-extra": "^8.0.1"
   }
 }

+ 63 - 21
packages/admin-ui-plugin/src/plugin.ts

@@ -1,5 +1,10 @@
 import { DEFAULT_AUTH_TOKEN_HEADER_KEY } from '@vendure/common/lib/shared-constants';
-import { AdminUiApp, AdminUiAppDevMode, AdminUiConfig, Type } from '@vendure/common/lib/shared-types';
+import {
+    AdminUiAppConfig,
+    AdminUiAppDevModeConfig,
+    AdminUiConfig,
+    Type,
+} from '@vendure/common/lib/shared-types';
 import {
     AuthOptions,
     ConfigService,
@@ -43,7 +48,7 @@ export interface AdminUiOptions {
      * 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.
      */
-    app?: AdminUiApp | AdminUiAppDevMode;
+    app?: AdminUiAppConfig | AdminUiAppDevModeConfig;
     /**
      * @description
      * The hostname of the Vendure server which the admin ui will be making API calls
@@ -101,7 +106,6 @@ export interface AdminUiOptions {
 export class AdminUiPlugin implements OnVendureBootstrap, OnVendureClose {
     private static options: AdminUiOptions;
     private server: Server;
-    private devServerClose: () => void | Promise<void> | undefined;
 
     constructor(private configService: ConfigService) {}
 
@@ -155,7 +159,7 @@ export class AdminUiPlugin implements OnVendureBootstrap, OnVendureClose {
         const { adminApiPath, authOptions } = this.configService;
         const { apiHost, apiPort, port, app } = AdminUiPlugin.options;
         const adminUiAppPath = AdminUiPlugin.isDevModeApp(app)
-            ? app.sourcePath
+            ? path.join(app.sourcePath, 'src')
             : (app && app.path) || DEFAULT_APP_PATH;
         const adminUiConfigPath = path.join(adminUiAppPath, 'vendure-ui-config.json');
         const overwriteConfig = () =>
@@ -176,30 +180,36 @@ export class AdminUiPlugin implements OnVendureBootstrap, OnVendureClose {
             });
             this.server = adminUiServer.listen(AdminUiPlugin.options.port);
             if (app && typeof app.compile === 'function') {
-                Logger.info(`Compiling Admin UI app in production mode`, loggerCtx);
+                Logger.info(`Compiling Admin UI app in production mode...`, loggerCtx);
                 app.compile()
                     .then(overwriteConfig)
-                    .then(() => {
-                        Logger.info(`Admin UI successfully compiled`);
-                    });
+                    .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(overwriteConfig)
-                .then(() => {
-                    Logger.info(`Admin UI successfully compiled and watching for changes...`);
-                });
+            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();
         }
     }
 
     /** @internal */
     async onVendureClose(): Promise<void> {
-        if (this.devServerClose) {
-            await this.devServerClose();
-        }
         if (this.server) {
             await new Promise(resolve => this.server.close(() => resolve()));
         }
@@ -217,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);
         }
@@ -229,14 +269,16 @@ export class AdminUiPlugin implements OnVendureBootstrap, OnVendureClose {
         config.adminApiPath = adminApiPath;
         config.tokenMethod = authOptions.tokenMethod || 'cookie';
         config.authTokenHeaderKey = authOptions.authTokenHeaderKey || DEFAULT_AUTH_TOKEN_HEADER_KEY;
-        Logger.verbose(`Applying configuration to vendure-ui-config.json file`, loggerCtx);
         await fs.writeFile(adminUiConfigPath, JSON.stringify(config, null, 2));
+        Logger.verbose(`Applied configuration to vendure-ui-config.json file`, loggerCtx);
     }
 
-    private static isDevModeApp(app?: AdminUiApp | AdminUiAppDevMode): app is AdminUiAppDevMode {
+    private static isDevModeApp(
+        app?: AdminUiAppConfig | AdminUiAppDevModeConfig,
+    ): app is AdminUiAppDevModeConfig {
         if (!app) {
             return false;
         }
-        return typeof (app as any).close === 'function' && typeof (app as any).sourcePath === 'string';
+        return !!(app as AdminUiAppDevModeConfig).sourcePath;
     }
 }

+ 1 - 1
packages/admin-ui/.gitignore

@@ -4,7 +4,7 @@
 /dist
 /tmp
 /out-tsc
-/library
+/package
 /src/lib/package.json
 
 # dependencies

+ 2 - 0
packages/admin-ui/README.md

@@ -19,6 +19,8 @@ of a set of modules which are accessible from consuming applications as sub-pack
 
 etc. These library packages are located at [./src/lib](./src/lib)
 
+When built with `yarn build`, the output will be located in the `./package` sub directory. This is also the root of the published npm package.
+
 ### Application
 
 In addition to the library, there is also a full application located at [./src/app](./src/app). This application is used both during development of the Admin UI, and also as the "default" Admin UI without any UI extensions, as provided as the default by the [admin-ui-plugin](../admin-ui-plugin).

+ 20 - 30
packages/admin-ui/package.json

@@ -15,28 +15,18 @@
   },
   "publishConfig": {
     "access": "public",
-    "directory": "dist"
+    "directory": "package"
   },
-  "main": "./library/bundles/vendure-admin-ui.umd.js",
-  "module": "./library/fesm5/vendure-admin-ui.js",
-  "es2015": "./library/fesm2015/vendure-admin-ui.js",
-  "esm5": "./library/esm5/vendure-admin-ui.js",
-  "esm2015": "./library/esm2015/vendure-admin-ui.js",
-  "fesm5": "./library/fesm5/vendure-admin-ui.js",
-  "fesm2015": "./library/fesm2015/vendure-admin-ui.js",
-  "typings": "./library/vendure-admin-ui.d.ts",
-  "sideEffects": false,
   "dependencies": {
-    "@angular/animations": "^9.0.4",
-    "@angular/cdk": "^9.1.0",
-    "@angular/common": "^9.0.4",
-    "@angular/core": "^9.0.4",
-    "@angular/forms": "^9.0.4",
-    "@angular/language-service": "^9.0.4",
-    "@angular/platform-browser": "^9.0.4",
-    "@angular/platform-browser-dynamic": "^9.0.4",
-    "@angular/router": "^9.0.4",
-    "@vendure/common": "^0.9.0",
+    "@angular/animations": "9.0.6",
+    "@angular/cdk": "9.1.0",
+    "@angular/common": "9.0.6",
+    "@angular/core": "9.0.6",
+    "@angular/forms": "9.0.6",
+    "@angular/language-service": "9.0.6",
+    "@angular/platform-browser": "9.0.6",
+    "@angular/platform-browser-dynamic": "9.0.6",
+    "@angular/router": "9.0.6",
     "@biesbjerg/ngx-translate-extract-marker": "^1.0.0",
     "@clr/angular": "^3.0.0",
     "@clr/core": "^3.0.0",
@@ -45,7 +35,8 @@
     "@ng-select/ng-select": "^3.7.2",
     "@ngx-translate/core": "^11.0.1",
     "@ngx-translate/http-loader": "^4.0.0",
-    "@vendure/ui-devkit": "^0.9.0",
+    "@vendure/common": "^0.10.13",
+    "@vendure/ui-devkit": "^0.10.13",
     "@webcomponents/custom-elements": "^1.2.4",
     "apollo-angular": "^1.8.0",
     "apollo-cache-inmemory": "^1.6.5",
@@ -53,10 +44,8 @@
     "apollo-link": "^1.2.13",
     "apollo-link-context": "^1.0.19",
     "apollo-upload-client": "^12.1.0",
-    "chokidar": "^3.0.2",
     "core-js": "^3.1.3",
     "dayjs": "^1.8.20",
-    "fs-extra": "^8.1.0",
     "graphql": "^14.6.0",
     "graphql-tag": "^2.10.3",
     "messageformat": "2.2.0",
@@ -74,15 +63,14 @@
     "prosemirror-state": "^1.0.0",
     "rxjs": "^6.5.4",
     "tslib": "^1.10.0",
-    "typescript": "~3.7.5",
     "zone.js": "~0.10.2"
   },
   "devDependencies": {
-    "@angular/cli": "^9.0.4",
-    "@angular/compiler": "^9.0.4",
-    "@angular/compiler-cli": "^9.0.4",
-    "@angular-devkit/build-angular": "~0.900.4",
-    "@angular-devkit/build-ng-packagr": "~0.900.4",
+    "@angular-devkit/build-angular": "~0.900.5",
+    "@angular-devkit/build-ng-packagr": "~0.900.5",
+    "@angular/cli": "^9.0.5",
+    "@angular/compiler": "^9.0.6",
+    "@angular/compiler-cli": "^9.0.6",
     "@biesbjerg/ngx-translate-extract": "^4.2.0",
     "@types/jasmine": "~3.3.16",
     "@types/jasminewd2": "~2.0.6",
@@ -92,6 +80,7 @@
     "@types/prosemirror-state": "^1.2.3",
     "@types/prosemirror-view": "^1.11.2",
     "codelyzer": "^5.1.2",
+    "fs-extra": "^8.1.0",
     "jasmine-core": "~3.4.0",
     "jasmine-spec-reporter": "~4.2.1",
     "karma": "~4.1.0",
@@ -104,6 +93,7 @@
     "protractor": "~5.4.2",
     "puppeteer": "^1.19.0",
     "rimraf": "^3.0.0",
-    "tslint": "^5.12.1"
+    "tslint": "^5.12.1",
+    "typescript": "~3.7.5"
   }
 }

+ 5 - 3
packages/admin-ui/scripts/copy-package-json.js

@@ -2,7 +2,9 @@ const path = require('path');
 const fs = require('fs');
 // Copies the main package.json file into the lib directory so that
 // ng-packagr can use it when generating the library bundle
-
 console.log('Copying main package.json to library...');
-const packageJson = require('../package');
-fs.writeFileSync(path.join(__dirname, '/../src/lib/package.json'), JSON.stringify(packageJson, null, 2), 'utf8');
+const packageJson = require('../package.json');
+const { name, version, license, dependencies } = packageJson;
+const subset = { name, version, license, dependencies };
+
+fs.writeFileSync(path.join(__dirname, '/../src/lib/package.json'), JSON.stringify(subset, null, 2), 'utf8');

+ 1 - 1
packages/admin-ui/src/lib/ng-package.json

@@ -1,6 +1,6 @@
 {
   "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
-  "dest": "../../library",
+  "dest": "../../package",
   "whitelistedNonPeerDependencies": ["."],
   "assets": [
     "./static/favicon.ico",

+ 9 - 105
packages/common/src/shared-types.ts

@@ -22,7 +22,7 @@ export type DeepRequired<T, U extends object | undefined = undefined> = T extend
     ? {
           [P in keyof T]-?: NonNullable<T[P]> extends NonNullable<U | Function | Type<any>>
               ? NonNullable<T[P]>
-              : DeepRequired<NonNullable<T[P]>, U>;
+              : DeepRequired<NonNullable<T[P]>, U>
       }
     : T;
 // tslint:enable:ban-types
@@ -96,7 +96,7 @@ export interface AdminUiConfig {
  *
  * @docsCategory common
  */
-export interface AdminUiApp {
+export interface AdminUiAppConfig {
     /**
      * @description
      * The path to the compiled admin ui app files. If not specified, an internal
@@ -104,6 +104,10 @@ export interface AdminUiApp {
      * index.html, the compiled js bundles etc.
      */
     path: string;
+    /**
+     * @description
+     * The function which will be invoked to start the app compilation process.
+     */
     compile?: () => Promise<void>;
 }
 
@@ -113,7 +117,7 @@ export interface AdminUiApp {
  *
  * @docsCategory common
  */
-export interface AdminUiAppDevMode {
+export interface AdminUiAppDevModeConfig {
     /**
      * @description
      * The path to the uncompiled ui app source files. This path should contain the `vendure-ui-config.json` file.
@@ -124,109 +128,9 @@ export interface AdminUiAppDevMode {
      * The port on which the dev server is listening. Overrides the value set by `AdminUiOptions.port`.
      */
     port: number;
-    compile: () => Promise<void>;
-    /**
-     * @description
-     * If this function is specified, it will be invoked when the plugin closes. Intended for
-     * ensuring the dev server is shut down as part of the AdminUiPlugin lifecycle.
-     */
-    onClose?: () => void | Promise<void>;
-}
-
-/**
- * @description
- * Defines extensions to the Admin UI application by specifying additional
- * Angular [NgModules](https://angular.io/guide/ngmodules) which are compiled
- * into the application.
- *
- * See [Extending the Admin UI](/docs/developer-guide/plugins/extending-the-admin-ui/) for
- * detailed instructions.
- *
- * @docsCategory AdminUiPlugin
- */
-export interface AdminUiExtension {
-    /**
-     * @description
-     * An optional ID for the extension module. Only used internally for generating
-     * import paths to your module. If not specified, a unique hash will be used as the id.
-     */
-    id?: string;
-
-    /**
-     * @description
-     * The path to the directory containing the extension module(s). The entire contents of this directory
-     * will be copied into the Admin UI app, including all TypeScript source files, html templates,
-     * scss style sheets etc.
-     */
-    extensionPath: string;
     /**
      * @description
-     * One or more Angular modules which extend the default Admin UI.
+     * The function which will be invoked to start the app compilation process.
      */
-    ngModules: Array<AdminUiExtensionSharedModule | AdminUiExtensionLazyModule>;
-
-    /**
-     * @description
-     * Optional array of paths to static assets which will be copied over to the Admin UI app's `/static`
-     * directory.
-     */
-    staticAssets?: string[];
-}
-
-/**
- * @description
- * Configuration defining a single NgModule with which to extend the Admin UI.
- *
- * @docsCategory AdminUiPlugin
- */
-export interface AdminUiExtensionSharedModule {
-    /**
-     * @description
-     * Shared modules are directly imported into the main AppModule of the Admin UI
-     * and should be used to declare custom form components and define custom
-     * navigation items.
-     */
-    type: 'shared';
-    /**
-     * @description
-     * The name of the file containing the extension module class.
-     */
-    ngModuleFileName: string;
-    /**
-     * @description
-     * The name of the extension module class.
-     */
-    ngModuleName: string;
-}
-
-/**
- * @description
- * Configuration defining a single NgModule with which to extend the Admin UI.
- *
- * @docsCategory AdminUiPlugin
- */
-export interface AdminUiExtensionLazyModule {
-    /**
-     * @description
-     * Lazy modules are lazy-loaded at the `/extensions/` route and should be used for
-     * modules which define new views for the Admin UI.
-     */
-    type: 'lazy';
-    /**
-     * @description
-     * The route specifies the route at which the module will be lazy-loaded. E.g. a value
-     * of `'foo'` will cause the module to lazy-load when the `/extensions/foo` route
-     * is activated.
-     */
-    route: string;
-    /**
-     * @description
-     * The name of the file containing the extension module class.
-     */
-    ngModuleFileName: string;
-    /**
-     * @description
-     * The name of the extension module class.
-     */
-    ngModuleName: string;
+    compile: () => Promise<void>;
 }

+ 11 - 11
packages/ui-devkit/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/ui-devkit",
-  "version": "0.9.0",
+  "version": "0.10.13",
   "description": "A library for authoring Vendure Admin UI extensions",
   "keywords": [
     "vendure",
@@ -10,14 +10,14 @@
   "author": "Michael Bromley <michael@michaelbromley.co.uk>",
   "homepage": "https://github.com/vendure-ecommerce/vendure#readme",
   "license": "MIT",
-  "directories": {
-    "lib": "lib"
-  },
   "files": [
     "client",
     "compiler",
     "scaffold"
   ],
+  "publishConfig": {
+    "access": "public"
+  },
   "main": "client/index.js",
   "types": "client/index.d.ts",
   "repository": {
@@ -35,13 +35,13 @@
     "url": "https://github.com/vendure-ecommerce/vendure/issues"
   },
   "dependencies": {
-    "@angular/cli": "^9.0.4",
-    "@angular/compiler": "^9.0.4",
-    "@angular/compiler-cli": "^9.0.4",
-    "@angular-devkit/build-angular": "~0.900.4",
-    "@vendure/common": "^0.9.0",
-    "@vendure/admin-ui": "^0.9.0",
-    "rxjs": "^6.5.3"
+    "@angular-devkit/build-angular": "~0.900.5",
+    "@angular/cli": "^9.0.5",
+    "@angular/compiler": "^9.0.6",
+    "@angular/compiler-cli": "^9.0.6",
+    "@vendure/common": "^0.10.15",
+    "chokidar": "^3.0.2",
+    "rxjs": "^6.5.4"
   },
   "devDependencies": {
     "@rollup/plugin-node-resolve": "^6.0.0",

+ 7 - 0
packages/ui-devkit/scaffold/README.md

@@ -0,0 +1,7 @@
+# Generated Admin UI
+
+This directory and its entire contents was generated the `compileUiExtensions()` function of the `@vendure/ui-devkit/compiler` package. It is not recommended to modify these files, since any changes will be overwritten upon re-compiling the ui extensions.
+
+## Production app
+
+When compiling in production mode (`devMode: false`), the compiled application will be output to the `./dist` directory. This is a production-ready Angular application which can then be served from any web server, with attention to the [Angular server configuration guide](https://angular.io/guide/deployment#server-configuration).

+ 0 - 14
packages/ui-devkit/scaffold/ngcc.config.js

@@ -1,14 +0,0 @@
-// Prevents false positive warnings from the ng compatibility compiler.
-// See https://github.com/angular/angular/pull/35683
-module.exports = {
-    packages: {
-        '@vendure/admin-ui': {
-            ignorableDeepImportMatchers: [
-                /@vendure\/common\//,
-                /@clr\/icons\//,
-                /@webcomponents\//,
-                /graphql\//,
-            ]
-        },
-    }
-};

+ 1 - 1
packages/ui-devkit/scaffold/src/app.routes.ts

@@ -39,7 +39,7 @@ export const routes: Route[] = [
                 path: 'settings',
                 loadChildren: () => import('@vendure/admin-ui/settings').then(m => m.SettingsModule),
             },
+            ...extensionRoutes,
         ],
-        ...extensionRoutes,
     },
 ];

+ 0 - 6
packages/ui-devkit/scaffold/src/main.ts

@@ -5,12 +5,6 @@ import { loadAppConfig } from '@vendure/admin-ui/core';
 import { AppModule } from './app.module';
 import { environment } from './environment';
 
-// Using TS "import" results in the following error when building with the Angular CLI:
-// "Error: <path>\node_modules\@vendure\admin-ui\library\app\app.module.d.ts is missing from the
-// TypeScript compilation. Please make sure it is in your tsconfig via the 'files' or 'include' property."
-// tslint:disable:no-var-requires
-declare const require: any;
-
 if (environment.production) {
     enableProdMode();
 }

+ 1 - 1
packages/ui-devkit/scaffold/src/tsconfig.app.json

@@ -12,7 +12,7 @@
   ],
   "angularCompilerOptions": {
     "strictMetadataEmit": true,
-    "fullTemplateTypeCheck": true,
+    "fullTemplateTypeCheck": false,
     "strictInjectionParameters": true
   }
 }

+ 42 - 27
packages/ui-devkit/src/compiler/compile.ts

@@ -1,5 +1,5 @@
 /* tslint:disable:no-console */
-import { AdminUiApp, AdminUiAppDevMode } from '@vendure/common/lib/shared-types';
+import { AdminUiAppConfig, AdminUiAppDevModeConfig } from '@vendure/common/lib/shared-types';
 import { ChildProcess, execSync, spawn } from 'child_process';
 import { FSWatcher, watch as chokidarWatch } from 'chokidar';
 import { createHash } from 'crypto';
@@ -23,18 +23,18 @@ const SHARED_EXTENSIONS_FILE = 'src/shared-extensions.module.ts';
  */
 export function compileUiExtensions({
     outputPath,
-    watch,
+    devMode,
     watchPort,
     extensions,
-}: UiExtensionCompilerOptions): AdminUiApp | AdminUiAppDevMode {
-    if (watch) {
+}: UiExtensionCompilerOptions): AdminUiAppConfig | AdminUiAppDevModeConfig {
+    if (devMode) {
         return runWatchMode(outputPath, watchPort || 4200, extensions);
     } else {
         return runCompileMode(outputPath, extensions);
     }
 }
 
-function runCompileMode(outputPath: string, extensions: AdminUiExtension[]): AdminUiApp {
+function runCompileMode(outputPath: string, extensions: AdminUiExtension[]): AdminUiAppConfig {
     const cmd = shouldUseYarn() ? 'yarn' : 'npm';
     const distPath = path.join(outputPath, 'dist');
 
@@ -62,7 +62,11 @@ function runCompileMode(outputPath: string, extensions: AdminUiExtension[]): Adm
     };
 }
 
-function runWatchMode(outputPath: string, port: number, extensions: AdminUiExtension[]): AdminUiAppDevMode {
+function runWatchMode(
+    outputPath: string,
+    port: number,
+    extensions: AdminUiExtension[],
+): AdminUiAppDevModeConfig {
     const cmd = shouldUseYarn() ? 'yarn' : 'npm';
     const devkitPath = require.resolve('@vendure/ui-devkit');
     let buildProcess: ChildProcess;
@@ -134,11 +138,13 @@ function runWatchMode(outputPath: string, port: number, extensions: AdminUiExten
         if (watcher) {
             watcher.close();
         }
-        buildProcess.kill();
+        if (buildProcess) {
+            buildProcess.kill();
+        }
     };
 
     process.on('SIGINT', close);
-    return { sourcePath: outputPath, port, onClose: close, compile };
+    return { sourcePath: outputPath, port, compile };
 }
 
 async function setupScaffold(outputPath: string, extensions: AdminUiExtension[]) {
@@ -208,7 +214,7 @@ function generateLazyExtensionRoutes(extensions: Array<Required<AdminUiExtension
         for (const module of extension.ngModules) {
             if (module.type === 'lazy') {
                 routes.push(`  {
-    path: module.route,
+    path: 'extensions/${module.route}',
     loadChildren: () => import('${getModuleFilePath(extension.id, module)}').then(m => m.${
                     module.ngModuleName
                 }),
@@ -222,20 +228,23 @@ function generateLazyExtensionRoutes(extensions: Array<Required<AdminUiExtension
 function generateSharedExtensionModule(extensions: Array<Required<AdminUiExtension>>) {
     return `import { NgModule } from '@angular/core';
 import { CommonModule } from '@angular/common';
-${extensions.map(e =>
-    e.ngModules
-        .filter(m => m.type === 'shared')
-        .map(m => `import { ${m.ngModuleName} } from '${getModuleFilePath(e.id, m)}';`)
-        .join('\n'),
-)}
-
-@NgModule({
-    imports: [CommonModule, ${extensions.map(e =>
+${extensions
+    .map(e =>
         e.ngModules
             .filter(m => m.type === 'shared')
-            .map(m => m.ngModuleName)
-            .join(', '),
-    )}],
+            .map(m => `import { ${m.ngModuleName} } from '${getModuleFilePath(e.id, m)}';\n`),
+    )
+    .join('')}
+
+@NgModule({
+    imports: [CommonModule, ${extensions
+        .map(e =>
+            e.ngModules
+                .filter(m => m.type === 'shared')
+                .map(m => m.ngModuleName)
+                .join(', '),
+        )
+        .join(', ')}],
 })
 export class SharedExtensionsModule {}
 `;
@@ -245,7 +254,7 @@ function getModuleFilePath(
     id: string,
     module: AdminUiExtensionLazyModule | AdminUiExtensionSharedModule,
 ): string {
-    return `./extensions/${id}/${path.basename(module.ngModuleFileName, 'ts')}`;
+    return `./extensions/${id}/${path.basename(module.ngModuleFileName, '.ts')}`;
 }
 
 /**
@@ -283,7 +292,7 @@ function copySourceIfNotExists(outputPath: string) {
         return;
     }
     const scaffoldDir = path.join(__dirname, '../scaffold');
-    const adminUiSrc = path.join(__dirname, '../../admin-ui/static');
+    const adminUiSrc = path.join(require.resolve('@vendure/admin-ui'), '../../static');
 
     if (!fs.existsSync(scaffoldDir)) {
         throw new Error(`Could not find the admin ui scaffold files at ${scaffoldDir}`);
@@ -317,9 +326,9 @@ async function checkIfNgccWasRun(): Promise<void> {
         console.log(`Could not resolve the "@vendure/admin-ui/core" package!`);
         return;
     }
-    // ngcc creates backup files when it has been run
-    const ivyFile = coreUmdFile + '.__ivy_ngcc_bak';
-    if (fs.existsSync(ivyFile)) {
+    // ngcc creates a particular folder after it has been run once
+    const ivyDir = path.join(coreUmdFile, '../..', '__ivy_ngcc__');
+    if (fs.existsSync(ivyDir)) {
         return;
     }
     // Looks like ngcc has not been run, so attempt to do so.
@@ -332,7 +341,13 @@ async function checkIfNgccWasRun(): Promise<void> {
         const cmd = shouldUseYarn() ? 'yarn' : 'npx';
         const ngccProcess = spawn(
             cmd,
-            ['ngcc', ' --properties es2015 browser module main', '--first-only', '--create-ivy-entry-points'],
+            [
+                'ngcc',
+                '--properties es2015 browser module main',
+                '--first-only',
+                '--create-ivy-entry-points',
+                '-l=error',
+            ],
             {
                 cwd: rootDir,
                 shell: true,

+ 2 - 2
packages/ui-devkit/src/compiler/types.ts

@@ -117,13 +117,13 @@ export interface UiExtensionCompilerOptions {
     /**
      * @description
      * Set to `true` in order to compile 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
+     * [ng serve](https://angular.io/cli/serve) command). When in dev 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;
+    devMode?: boolean;
     /**
      * @description
      * In watch mode, allows the port of the dev server to be specified. Defaults to the Angular CLI default

+ 3 - 3
scripts/publish-to-verdaccio.sh

@@ -11,12 +11,12 @@ fi
 echo "Publishing to Verdaccio @ $VERDACCIO"
 
 cd ../packages/admin-ui-plugin && npm publish -reg $VERDACCIO &&\
-cd ../admin-ui/library && npm publish -reg $VERDACCIO &&\
-cd ../../asset-server-plugin && npm publish -reg $VERDACCIO &&\
+cd ../asset-server-plugin && npm publish -reg $VERDACCIO &&\
 cd ../common && npm publish -reg $VERDACCIO &&\
 cd ../core && npm publish -reg $VERDACCIO &&\
 cd ../create && npm publish -reg $VERDACCIO &&\
 cd ../elasticsearch-plugin && npm publish -reg $VERDACCIO &&\
 cd ../email-plugin && npm publish -reg $VERDACCIO &&\
 cd ../testing && npm publish -reg $VERDACCIO &&\
-cd ../ui-devkit && npm publish -reg $VERDACCIO
+cd ../ui-devkit && npm publish -reg $VERDACCIO &&\
+cd ../admin-ui/package && npm publish -reg $VERDACCIO

+ 1 - 180
yarn.lock

@@ -2,14 +2,6 @@
 # yarn lockfile v1
 
 
-"@angular-devkit/architect@0.900.3":
-  version "0.900.3"
-  resolved "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.900.3.tgz#9c396733abd12fbb1d5bbc4542b2ee52418adb02"
-  integrity sha512-4UHc58Dlc5XHY3eiYSX9gytLyPNYixGSRwLcc/LRwuPgrmUFKPzCN3nwgB+9kc03/HN89CsJ1rS1scid6N6vxQ==
-  dependencies:
-    "@angular-devkit/core" "9.0.3"
-    rxjs "6.5.3"
-
 "@angular-devkit/architect@0.900.4":
   version "0.900.4"
   resolved "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.900.4.tgz#6daf95b9c2ec51c38ca451c3ff7cf64e5404a04a"
@@ -18,73 +10,6 @@
     "@angular-devkit/core" "9.0.4"
     rxjs "6.5.3"
 
-"@angular-devkit/build-angular@~0.900.3":
-  version "0.900.3"
-  resolved "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.900.3.tgz#8c68b2d98908685ec29b6eb61aa44739f004b247"
-  integrity sha512-QrtHbSJSQ9FlcwlndagYzolOepVKeBX+ZZJgCHDyJTUtZG/ah1N7lfmq+gp1IXdiVEk17H9YAk9iZd48lKBO9g==
-  dependencies:
-    "@angular-devkit/architect" "0.900.3"
-    "@angular-devkit/build-optimizer" "0.900.3"
-    "@angular-devkit/build-webpack" "0.900.3"
-    "@angular-devkit/core" "9.0.3"
-    "@babel/core" "7.7.7"
-    "@babel/generator" "7.7.7"
-    "@babel/preset-env" "7.7.7"
-    "@ngtools/webpack" "9.0.3"
-    ajv "6.10.2"
-    autoprefixer "9.7.1"
-    babel-loader "8.0.6"
-    browserslist "4.8.3"
-    cacache "13.0.1"
-    caniuse-lite "1.0.30001020"
-    circular-dependency-plugin "5.2.0"
-    copy-webpack-plugin "5.1.1"
-    core-js "3.6.0"
-    coverage-istanbul-loader "2.0.3"
-    cssnano "4.1.10"
-    file-loader "4.2.0"
-    find-cache-dir "3.0.0"
-    glob "7.1.5"
-    jest-worker "24.9.0"
-    karma-source-map-support "1.4.0"
-    less "3.10.3"
-    less-loader "5.0.0"
-    license-webpack-plugin "2.1.3"
-    loader-utils "1.2.3"
-    magic-string "0.25.4"
-    mini-css-extract-plugin "0.8.0"
-    minimatch "3.0.4"
-    open "7.0.0"
-    parse5 "4.0.0"
-    postcss "7.0.21"
-    postcss-import "12.0.1"
-    postcss-loader "3.0.0"
-    raw-loader "3.1.0"
-    regenerator-runtime "0.13.3"
-    rimraf "3.0.0"
-    rollup "1.25.2"
-    rxjs "6.5.3"
-    sass "1.23.3"
-    sass-loader "8.0.0"
-    semver "6.3.0"
-    source-map "0.7.3"
-    source-map-loader "0.2.4"
-    source-map-support "0.5.16"
-    speed-measure-webpack-plugin "1.3.1"
-    style-loader "1.0.0"
-    stylus "0.54.7"
-    stylus-loader "3.0.2"
-    terser "4.5.1"
-    terser-webpack-plugin "2.3.3"
-    tree-kill "1.2.2"
-    webpack "4.41.2"
-    webpack-dev-middleware "3.7.2"
-    webpack-dev-server "3.9.0"
-    webpack-merge "4.2.2"
-    webpack-sources "1.4.3"
-    webpack-subresource-integrity "1.3.4"
-    worker-plugin "3.2.0"
-
 "@angular-devkit/build-angular@~0.900.4":
   version "0.900.4"
   resolved "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.900.4.tgz#571bf28478c9242ca652adda5308f7697eeb4038"
@@ -160,17 +85,6 @@
     "@angular-devkit/architect" "0.900.4"
     rxjs "6.5.3"
 
-"@angular-devkit/build-optimizer@0.900.3":
-  version "0.900.3"
-  resolved "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.900.3.tgz#91f90c56affb0be9f7910dfc1d414f16c21c2c3f"
-  integrity sha512-VLAWtAXpOzOoYUJrN6sT90UdIdvrVIipkzGz7nfI1kscDvxUFwVZnsNNHtFinaY2SfZAunHhYQOA/B9FJ8WPdQ==
-  dependencies:
-    loader-utils "1.2.3"
-    source-map "0.7.3"
-    tslib "1.10.0"
-    typescript "3.6.4"
-    webpack-sources "1.4.3"
-
 "@angular-devkit/build-optimizer@0.900.4":
   version "0.900.4"
   resolved "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.900.4.tgz#c3cad084f54cccfc9ef90dc8e24716de939b2eda"
@@ -182,15 +96,6 @@
     typescript "3.6.4"
     webpack-sources "1.4.3"
 
-"@angular-devkit/build-webpack@0.900.3":
-  version "0.900.3"
-  resolved "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.900.3.tgz#4a2fd13cebe190c091606e18397a1f7cccfab6bb"
-  integrity sha512-9gSTLWf7yq/XBOec0CtZcjNMsC7L8IuVDProBQHps2SvTfr982DtHfEge95J2lc9BjRbqidv+phImFsQ1J3mFA==
-  dependencies:
-    "@angular-devkit/architect" "0.900.3"
-    "@angular-devkit/core" "9.0.3"
-    rxjs "6.5.3"
-
 "@angular-devkit/build-webpack@0.900.4":
   version "0.900.4"
   resolved "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.900.4.tgz#af99221fe4f50e8326978caae67312f46557a897"
@@ -200,17 +105,6 @@
     "@angular-devkit/core" "9.0.4"
     rxjs "6.5.3"
 
-"@angular-devkit/core@9.0.3":
-  version "9.0.3"
-  resolved "https://registry.npmjs.org/@angular-devkit/core/-/core-9.0.3.tgz#a027862d2edd981afcc6245176e9f27768c631c9"
-  integrity sha512-3+abmv9K9d+BVgUAolYgoOqlGAA2Jb1pWo2biapSDG6KjUZHUCJdnsKigLtLorCdv0SrjTp56FFplkcqKsFQgA==
-  dependencies:
-    ajv "6.10.2"
-    fast-json-stable-stringify "2.0.0"
-    magic-string "0.25.4"
-    rxjs "6.5.3"
-    source-map "0.7.3"
-
 "@angular-devkit/core@9.0.4":
   version "9.0.4"
   resolved "https://registry.npmjs.org/@angular-devkit/core/-/core-9.0.4.tgz#e137052eea491f3bc0d50a2571c4449c620c2f80"
@@ -222,15 +116,6 @@
     rxjs "6.5.3"
     source-map "0.7.3"
 
-"@angular-devkit/schematics@9.0.3":
-  version "9.0.3"
-  resolved "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-9.0.3.tgz#e65fa1ce08a3d5ef0af594b623024439c1110a0d"
-  integrity sha512-BQnZtFQPLZZOijhuEndtzL6cOnhaE8nNxupkRHavWohOMStnLsRyvVJj6JVDkf37wvT5koqTNjHhbdMxcCRc6A==
-  dependencies:
-    "@angular-devkit/core" "9.0.3"
-    ora "4.0.2"
-    rxjs "6.5.3"
-
 "@angular-devkit/schematics@9.0.4":
   version "9.0.4"
   resolved "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-9.0.4.tgz#fc20fe40a575ee72ed6bee90608dbd0d0579f135"
@@ -252,32 +137,6 @@
   optionalDependencies:
     parse5 "^5.0.0"
 
-"@angular/cli@^9.0.3":
-  version "9.0.3"
-  resolved "https://registry.npmjs.org/@angular/cli/-/cli-9.0.3.tgz#114bf16fd00685d814840f6fece286e0f8f6f1e3"
-  integrity sha512-DYa2k6ihYmvfKgv2SE/OqP76D8EEHkIFcJ3ZgVdnxEyCmUXUD4zqOVDzDIK794BdditLF88g4Mezz142bn6XUA==
-  dependencies:
-    "@angular-devkit/architect" "0.900.3"
-    "@angular-devkit/core" "9.0.3"
-    "@angular-devkit/schematics" "9.0.3"
-    "@schematics/angular" "9.0.3"
-    "@schematics/update" "0.900.3"
-    "@yarnpkg/lockfile" "1.1.0"
-    ansi-colors "4.1.1"
-    debug "^4.1.1"
-    ini "1.3.5"
-    inquirer "7.0.0"
-    npm-package-arg "6.1.1"
-    npm-pick-manifest "3.0.2"
-    open "7.0.0"
-    pacote "9.5.8"
-    read-package-tree "5.3.1"
-    rimraf "3.0.0"
-    semver "6.3.0"
-    symbol-observable "1.2.0"
-    universal-analytics "^0.4.20"
-    uuid "^3.3.2"
-
 "@angular/cli@^9.0.4":
   version "9.0.4"
   resolved "https://registry.npmjs.org/@angular/cli/-/cli-9.0.4.tgz#21aa041292a321e6cc3e7c2e4f0e40aead71322d"
@@ -2538,16 +2397,6 @@
   dependencies:
     tslib "^1.9.0"
 
-"@ngtools/webpack@9.0.3":
-  version "9.0.3"
-  resolved "https://registry.npmjs.org/@ngtools/webpack/-/webpack-9.0.3.tgz#d05b5a15584909262a4db027919f03ccb074dc11"
-  integrity sha512-pMIXfq1IJLbvwmkPonGs7nrpuBCXrlZTf9A4OYsMBZcfU8JMn0pRdx7G2+bC9Q/f+uSw2uvPSv76xJXLBOntmA==
-  dependencies:
-    "@angular-devkit/core" "9.0.3"
-    enhanced-resolve "4.1.1"
-    rxjs "6.5.3"
-    webpack-sources "1.4.3"
-
 "@ngtools/webpack@9.0.4":
   version "9.0.4"
   resolved "https://registry.npmjs.org/@ngtools/webpack/-/webpack-9.0.4.tgz#edb6a89192f33fc639ba677a34f19faf7c3f4289"
@@ -2780,14 +2629,6 @@
   dependencies:
     any-observable "^0.3.0"
 
-"@schematics/angular@9.0.3":
-  version "9.0.3"
-  resolved "https://registry.npmjs.org/@schematics/angular/-/angular-9.0.3.tgz#8b0fb91fa18dd909001ac0d888479a96810aa640"
-  integrity sha512-6XSnPW4G7aoKXccg0FTpZ02y/yi9y/bj7swnSL9Z4RRPIvPVapDjB7uJPg8sm8+PTIpcMhEFQrchIqM3LXW4zA==
-  dependencies:
-    "@angular-devkit/core" "9.0.3"
-    "@angular-devkit/schematics" "9.0.3"
-
 "@schematics/angular@9.0.4":
   version "9.0.4"
   resolved "https://registry.npmjs.org/@schematics/angular/-/angular-9.0.4.tgz#e90e15abc512c310e3865717811fc3152f3462b1"
@@ -2796,21 +2637,6 @@
     "@angular-devkit/core" "9.0.4"
     "@angular-devkit/schematics" "9.0.4"
 
-"@schematics/update@0.900.3":
-  version "0.900.3"
-  resolved "https://registry.npmjs.org/@schematics/update/-/update-0.900.3.tgz#9141ee2e1b6356e66f6269b92c284c86e4faf065"
-  integrity sha512-mlRsm3/HM1f/10Wdz4xMYA+mpW3EDCB+whlV5cJ7PGMhjUMaxA9DuWvoP06h05le6XmgnjIEoxL6NJ7CgesHcA==
-  dependencies:
-    "@angular-devkit/core" "9.0.3"
-    "@angular-devkit/schematics" "9.0.3"
-    "@yarnpkg/lockfile" "1.1.0"
-    ini "1.3.5"
-    npm-package-arg "^7.0.0"
-    pacote "9.5.8"
-    rxjs "6.5.3"
-    semver "6.3.0"
-    semver-intersect "1.4.0"
-
 "@schematics/update@0.900.4":
   version "0.900.4"
   resolved "https://registry.npmjs.org/@schematics/update/-/update-0.900.4.tgz#5a125f5dd359b5a21b87b346d6dc765ae93f0a32"
@@ -6289,11 +6115,6 @@ core-js-compat@^3.6.0:
     browserslist "^4.8.3"
     semver "7.0.0"
 
-core-js@3.6.0:
-  version "3.6.0"
-  resolved "https://registry.npmjs.org/core-js/-/core-js-3.6.0.tgz#2b854e451de1967d1e29896025cdc13a2518d9ea"
-  integrity sha512-AHPTNKzyB+YwgDWoSOCaid9PUSEF6781vsfiK8qUz62zRR448/XgK2NtCbpiUGizbep8Lrpt0Du19PpGGZvw3Q==
-
 core-js@3.6.4:
   version "3.6.4"
   resolved "https://registry.npmjs.org/core-js/-/core-js-3.6.4.tgz#440a83536b458114b9cb2ac1580ba377dc470647"
@@ -15648,7 +15469,7 @@ run-queue@^1.0.0, run-queue@^1.0.3:
   dependencies:
     aproba "^1.1.1"
 
-rxjs@6.5.3, rxjs@^6.3.3, rxjs@^6.4.0, rxjs@^6.5.1, rxjs@^6.5.2, rxjs@^6.5.3:
+rxjs@6.5.3, rxjs@^6.3.3, rxjs@^6.4.0, rxjs@^6.5.1, rxjs@^6.5.2:
   version "6.5.3"
   resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz#510e26317f4db91a7eb1de77d9dd9ba0a4899a3a"
   integrity sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==