Pārlūkot izejas kodu

docs: Generate docs for all public packages

Michael Bromley 6 gadi atpakaļ
vecāks
revīzija
b22f401abc

+ 4 - 2
.gitignore

@@ -18,9 +18,11 @@ docs/static/main.css*
 docs/static/intro.js*
 docs/static/intro.css*
 docs/public
-docs/content/docs/configuration/*
-!docs/content/docs/configuration/_index.md
+docs/content/docs/typescript-api/*
+!docs/content/docs/typescript-api/_index.md
 docs/content/docs/graphql-api/*
+docs/content/docs/plugins/*
+!docs/content/docs/plugins/*.md
 !docs/content/docs/graphql-api/_index.md
 !docs/content/docs/graphql-api/shop/_index.md
 !docs/content/docs/graphql-api/admin/_index.md

+ 0 - 26
docs/content/docs/configuration/_index.md

@@ -1,26 +0,0 @@
----
-title: "Configuration"
-weight: 9
-showtoc: false
----
-
-# Vendure Configuration Docs
-
-All configuration is done by way of the [`VendureConfig`]({{< ref "vendure-config" >}}) object which is passed to Vendure's `bootstrap()` function.
-
-```TypeScript
-bootstrap({
-    authOptions: {
-        sessionSecret: 'BD95F861369DCD684AA668A926E86F8B',
-    },
-    port: 3000,
-    apiPath: 'api',
-    // ...
-});
-```
-
-This section contains a description of all available configuration options for Vendure.
-
-{{% alert %}}
-All documentation in this section is auto-generated from the TypeScript source of the Vendure server.
-{{% /alert %}}

+ 1 - 1
docs/content/docs/plugins/_index.md

@@ -15,4 +15,4 @@ Plugins in Vendure allow one to:
 
 These abilities make plugins a very versatile and powerful means of implementing custom business requirements.
 
-This section details the built-in plugins which ship with Vendure as well as a guide to writing your own plugins.
+This section details the official Vendure plugins included in the main Vendure repo, as well as a guide on writing your own plugins for Vendure.

+ 0 - 19
docs/content/docs/plugins/admin-ui-plugin.md

@@ -1,19 +0,0 @@
----
-title: "AdminUiPlugin"
----
-
-# AdminUiPlugin
-
-This plugin starts a static server for the Admin UI app, and proxies it via the `/admin/` path of the main Vendure server.
-
-The Admin UI allows you to administer all aspects of your store, from inventory management to order tracking. It is the tool used by store administrators on a day-to-day basis for the management of the store.
-
-
-```ts 
-const config: VendureConfig = {
-  // Add an instance of the plugin to the plugins array
-  plugins: [
-    new AdminUiPlugin({ port: 3002 }),
-  ],
-};
-```

+ 0 - 67
docs/content/docs/plugins/default-asset-server-plugin.md

@@ -1,67 +0,0 @@
----
-title: "DefaultAssetServerPlugin"
----
-
-# DefaultAssetServerPlugin
-
-The `DefaultAssetServerPlugin` serves assets (images and other files) from the local file system. It can also perform on-the-fly image transformations and caches the results for subsequent calls.
-
-```ts
-const config: VendureConfig = {
-  // Add an instance of the plugin to the plugins array
-  plugins: [
-    new DefaultAssetServerPlugin({
-      route: 'assets',
-      assetUploadDir: path.join(__dirname, 'assets'),
-      port: 4000,
-    }),
-  ],
-};
-```
-
-The full configuration is documented at [DefaultAssetServerOptions]({{< relref "default-asset-server-options" >}})
-
-## Image transformation
-
-Asset preview images can be transformed (resized & cropped) on the fly by appending query parameters to the url:
-
-`http://localhost:3000/assets/some-asset.jpg?w=500&h=300&mode=resize`
-
-The above URL will return `some-asset.jpg`, resized to fit in the bounds of a 500px x 300px rectangle.
-
-### Preview mode
-
-The `mode` parameter can be either `crop` or `resize`. See the [ImageTransformMode]({{< relref "image-transform-mode" >}}) docs for details.
-
-### Transform presets
-
-Presets can be defined which allow a single preset name to be used instead of specifying the width, height and mode. Presets are configured via the DefaultAssetServerOptions [presets property]({{< relref "default-asset-server-options" >}}#presets).
-
-For example, defining the following preset:
-
-```ts
-new DefaultAssetServerPlugin({
-  // ...
-  presets: [
-    { name: 'my-preset', width: 85, height: 85, mode: 'crop' },
-  ],
-}),
-```
-
-means that a request to:
-
-`http://localhost:3000/assets/some-asset.jpg?preset=my-preset`
-
-is equivalent to:
-
-`http://localhost:3000/assets/some-asset.jpg?w=85&h=85&mode=crop`
-
-The DefaultAssetServerPlugin comes pre-configured with the following presets:
-
-name | width | height | mode
------|-------|--------|-----
-tiny | 50px | 50px | crop
-thumb | 150px | 150px | crop
-small | 300px | 300px | resize
-medium | 500px | 500px | resize
-large | 800px | 800px | resize

+ 0 - 67
docs/content/docs/plugins/default-email-plugin.md

@@ -1,67 +0,0 @@
----
-title: "DefaultEmailPlugin"
----
-
-# DefaultEmailPlugin
-
-The DefaultEmailPlugin configures the the [EmailOptions]({{< relref "email-options" >}}) to use an [MJML](https://mjml.io/)-based email generator and presents a simplified interface for typical email requirements.
-
-```ts 
-const config: VendureConfig = {
-  // Add an instance of the plugin to the plugins array
-  plugins: [
-    new DefaultEmailPlugin({
-      templatePath: path.join(__dirname, 'vendure/email/templates'),
-      transport: {
-        type: 'smtp',
-        host: 'smtp.example.com',
-        port: 587,
-        auth: {
-          user: 'username',
-          pass: 'password',
-        }
-      },
-    }),
-  ],
-};
-```
-
-## Customizing templates
-
-Emails are generated from templates which use [MJML](https://mjml.io/) syntax. MJML is an open-source HTML-like markup language which makes the task of creating responsive email markup simple. By default, the templates are installed to `<project root>/vendure/email/templates` and can be freely edited.
-
-Dynamic data such as the recipient's name or order items are specified using [Handlebars syntax](https://handlebarsjs.com/):
-
-```HTML
-<p>Dear {{ order.customer.firstName }} {{ order.customer.lastName }},</p>
-
-<p>Thank you for your order!</p>
-
-<mj-table cellpadding="6px">
-  {{#each order.lines }}
-    <tr class="order-row">
-      <td>{{ quantity }} x {{ productVariant.name }}</td>
-      <td>{{ productVariant.quantity }}</td>
-      <td>{{ formatMoney totalPrice }}</td>
-    </tr>
-  {{/each}}
-</mj-table>
-```
-
-### Handlebars helpers
-
-The following helper functions are available for use in email templates:
-
-* `formatMoney`: Formats an amount of money (which are always stored as integers in Vendure) as a decimal, e.g. `123` => `1.23`
-* `formatDate`: Formats a Date value with the [dateformat](https://www.npmjs.com/package/dateformat) package.
-
-## Dev mode
-
-For development, the `transport` option can be replaced by `devMode: true`. Doing so configures Vendure to use the [file transport]({{< relref "file-transport-options" >}}) and outputs emails as rendered HTML files in a directory named "test-emails" which is located adjacent to the directory configured in the `templatePath`.
-
-```ts 
-new DefaultEmailPlugin({
-  templatePath: path.join(__dirname, 'vendure/email/templates'),
-  devMode: true,
-})
-```

+ 13 - 0
docs/content/docs/typescript-api/_index.md

@@ -0,0 +1,13 @@
+---
+title: "TypeScript API"
+weight: 9
+showtoc: false
+---
+
+# Vendure TypeScript API Docs
+
+The Vendure TypeScript API is used when configuring the server (via the [`VendureConfig`]({{< ref "vendure-config" >}}) object) and when writing plugins that extend the core functionality of Vendure core.
+
+{{% alert %}}
+All documentation in this section is auto-generated from the TypeScript source of the Vendure server.
+{{% /alert %}}

+ 15 - 4
packages/admin-ui-plugin/src/plugin.ts

@@ -10,7 +10,7 @@ import path from 'path';
  * @description
  * Configuration options for the {@link AdminUiPlugin}.
  *
- * @docsCategory plugin
+ * @docsCategory AdminUiPlugin
  */
 export interface AdminUiOptions {
     /**
@@ -47,10 +47,21 @@ export interface AdminUiOptions {
 
 /**
  * @description
- * This plugin starts a static server for the Admin UI app, and proxies it via the `/admin/` path
- * of the main Vendure server.
+ * This plugin starts a static server for the Admin UI app, and proxies it via the `/admin/` path of the main Vendure server.
  *
- * @docsCategory plugin
+ * The Admin UI allows you to administer all aspects of your store, from inventory management to order tracking. It is the tool used by store administrators on a day-to-day basis for the management of the store.
+ *
+ * @example
+ * ```ts
+ * const config: VendureConfig = {
+ *   // Add an instance of the plugin to the plugins array
+ *   plugins: [
+ *     new AdminUiPlugin({ port: 3002 }),
+ *   ],
+ * };
+ * ```
+ *
+ * @docsCategory AdminUiPlugin
  */
 export class AdminUiPlugin implements VendurePlugin {
     private server: Server;

+ 73 - 9
packages/asset-server-plugin/src/plugin.ts

@@ -16,13 +16,13 @@ import { transformImage } from './transform-image';
  * * resize: Preserving aspect ratio, resizes the image to be as large as possible
  * while ensuring its dimensions are less than or equal to both those specified.
  *
- * @docsCategory plugin
+ * @docsCategory AssetServerPlugin
  */
 export type ImageTransformMode = 'crop' | 'resize';
 
 /**
  * @description
- * A configuration option for an image size preset for the DefaultAssetServerPlugin.
+ * A configuration option for an image size preset for the AssetServerPlugin.
  *
  * Presets allow a shorthand way to generate a thumbnail preview of an asset. For example,
  * the built-in "tiny" preset generates a 50px x 50px cropped preview, which can be accessed
@@ -34,7 +34,7 @@ export type ImageTransformMode = 'crop' | 'resize';
  *
  * `http://localhost:3000/assets/some-asset.jpg?w=50&h=50&mode=crop`
  *
- * @docsCategory plugin
+ * @docsCategory AssetServerPlugin
  */
 export interface ImageTransformPreset {
     name: string;
@@ -45,15 +45,15 @@ export interface ImageTransformPreset {
 
 /**
  * @description
- * The configuration options for the DefaultAssetServerPlugin.
+ * The configuration options for the AssetServerPlugin.
  *
- * @docsCategory plugin
+ * @docsCategory AssetServerPlugin
  */
 export interface AssetServerOptions {
     hostname?: string;
     /**
      * @description
-     * The local port that the server will run on. Note that the DefaultAssetServerPlugin
+     * The local port that the server will run on. Note that the AssetServerPlugin
      * includes a proxy server which allows the asset server to be accessed on the same
      * port as the main Vendure server.
      */
@@ -90,9 +90,73 @@ export interface AssetServerOptions {
 }
 
 /**
- * The AssetServerPlugin instantiates a static Express server which is used to
- * serve the assets. It can also perform on-the-fly image transformations and caches the
- * results for subsequent calls.
+ * @description
+ * The `AssetServerPlugin` serves assets (images and other files) from the local file system. It can also perform on-the-fly image transformations
+ * and caches the results for subsequent calls.
+ *
+ * @example
+ * ```ts
+ * const config: VendureConfig = {
+ *   // Add an instance of the plugin to the plugins array
+ *   plugins: [
+ *     new AssetServerPlugin({
+ *       route: 'assets',
+ *       assetUploadDir: path.join(__dirname, 'assets'),
+ *       port: 4000,
+ *     }),
+ *   ],
+ * };
+ * ```
+ *
+ * The full configuration is documented at [AssetServerOptions]({{< relref "asset-server-options" >}})
+ *
+ * ## Image transformation
+ *
+ * Asset preview images can be transformed (resized & cropped) on the fly by appending query parameters to the url:
+ *
+ * `http://localhost:3000/assets/some-asset.jpg?w=500&h=300&mode=resize`
+ *
+ * The above URL will return `some-asset.jpg`, resized to fit in the bounds of a 500px x 300px rectangle.
+ *
+ * ### Preview mode
+ *
+ * The `mode` parameter can be either `crop` or `resize`. See the [ImageTransformMode]({{< relref "image-transform-mode" >}}) docs for details.
+ *
+ * ### Transform presets
+ *
+ * Presets can be defined which allow a single preset name to be used instead of specifying the width, height and mode. Presets are
+ * configured via the AssetServerOptions [presets property]({{< relref "asset-server-options" >}}#presets).
+ *
+ * For example, defining the following preset:
+ *
+ * ```ts
+ * new AssetServerPlugin({
+ *   // ...
+ *   presets: [
+ *     { name: 'my-preset', width: 85, height: 85, mode: 'crop' },
+ *   ],
+ * }),
+ * ```
+ *
+ * means that a request to:
+ *
+ * `http://localhost:3000/assets/some-asset.jpg?preset=my-preset`
+ *
+ * is equivalent to:
+ *
+ * `http://localhost:3000/assets/some-asset.jpg?w=85&h=85&mode=crop`
+ *
+ * The AssetServerPlugin comes pre-configured with the following presets:
+ *
+ * name | width | height | mode
+ * -----|-------|--------|-----
+ * tiny | 50px | 50px | crop
+ * thumb | 150px | 150px | crop
+ * small | 300px | 300px | resize
+ * medium | 500px | 500px | resize
+ * large | 800px | 800px | resize
+ *
+ * @docsCategory AssetServerPlugin
  */
 export class AssetServerPlugin implements VendurePlugin {
     private server: Server;

+ 0 - 6
packages/core/e2e/config/test-config.ts

@@ -42,12 +42,6 @@ export const testConfig: VendureConfig = {
     paymentOptions: {
         paymentMethodHandlers: [],
     },
-    emailOptions: {
-        emailTemplatePath: __dirname,
-        transport: {
-            type: 'none',
-        },
-    },
     importExportOptions: {
         importAssetsDir: path.join(__dirname, '..', 'fixtures/assets'),
     },

+ 1 - 1
packages/core/src/config/vendure-config.ts

@@ -188,7 +188,7 @@ export interface OrderOptions {
  * The AssetOptions define how assets (images and other files) are named and stored, and how preview images are generated.
  *
  * {{% alert %}}
- * If you are using the `DefaultAssetServerPlugin`,
+ * If you are using the `AssetServerPlugin`,
  * it is not necessary to configure these options.
  * {{% /alert %}}
  *

+ 1 - 1
packages/email-plugin/src/default-email-types.ts

@@ -7,7 +7,7 @@ import { configEmailType, EmailTypes } from './types';
  * The possible types of email which are configured by default. These define the keys of the
  * {@link EmailTypes} object.
  *
- * @docsCategory email
+ * @docsCategory EmailPlugin
  */
 export type DefaultEmailType = 'order-confirmation' | 'email-verification' | 'password-reset';
 

+ 2 - 2
packages/email-plugin/src/email-context.ts

@@ -7,7 +7,7 @@ import { VendureEvent } from '@vendure/core';
  * It is used in the `templateContext` method of the {@link TemplateConfig} object
  * to define which data get passed to the email template engine for interpolation.
  *
- * @docsCategory email
+ * @docsCategory EmailPlugin
  */
 export class EmailContext<T extends string = any, E extends VendureEvent = any> {
     /**
@@ -65,7 +65,7 @@ export class EmailContext<T extends string = any, E extends VendureEvent = any>
 /**
  *
  *
- * @docsCateogry email
+ * @docsCateogry EmailPlugin
  */
 export class GeneratedEmailContext<T extends string = any, E extends VendureEvent = any> extends EmailContext<
     T,

+ 69 - 1
packages/email-plugin/src/plugin.ts

@@ -10,7 +10,75 @@ import { TemplateLoader } from './template-loader';
 import { EmailOptions, EmailPluginDevModeOptions, EmailPluginOptions, EmailTransportOptions, EmailTypeConfig } from './types';
 
 /**
- * Configures the server to use the Handlebars / MJML email generator.
+ * @description
+ * The EmailPlugin creates and sends transactional emails based on Vendure events. It uses an [MJML](https://mjml.io/)-based
+ * email generator to generate the email body and [Nodemailer](https://nodemailer.com/about/) to send the emais.
+ *
+ * @example
+ * ```ts
+ * const config: VendureConfig = {
+ *   // Add an instance of the plugin to the plugins array
+ *   plugins: [
+ *     new EmailPlugin({
+ *       templatePath: path.join(__dirname, 'vendure/email/templates'),
+ *       transport: {
+ *         type: 'smtp',
+ *         host: 'smtp.example.com',
+ *         port: 587,
+ *         auth: {
+ *           user: 'username',
+ *           pass: 'password',
+ *         }
+ *       },
+ *     }),
+ *   ],
+ * };
+ * ```
+ *
+ * ## Customizing templates
+ *
+ * Emails are generated from templates which use [MJML](https://mjml.io/) syntax. MJML is an open-source HTML-like markup
+ * language which makes the task of creating responsive email markup simple. By default, the templates are installed to
+ * `<project root>/vendure/email/templates` and can be freely edited.
+ *
+ * Dynamic data such as the recipient's name or order items are specified using [Handlebars syntax](https://handlebarsjs.com/):
+ *
+ * ```HTML
+ * <p>Dear {{ order.customer.firstName }} {{ order.customer.lastName }},</p>
+ *
+ * <p>Thank you for your order!</p>
+ *
+ * <mj-table cellpadding="6px">
+ *   {{#each order.lines }}
+ *     <tr class="order-row">
+ *       <td>{{ quantity }} x {{ productVariant.name }}</td>
+ *       <td>{{ productVariant.quantity }}</td>
+ *       <td>{{ formatMoney totalPrice }}</td>
+ *     </tr>
+ *   {{/each}}
+ * </mj-table>
+ * ```
+ *
+ * ### Handlebars helpers
+ *
+ * The following helper functions are available for use in email templates:
+ *
+ * * `formatMoney`: Formats an amount of money (which are always stored as integers in Vendure) as a decimal, e.g. `123` => `1.23`
+ * * `formatDate`: Formats a Date value with the [dateformat](https://www.npmjs.com/package/dateformat) package.
+ *
+ * ## Dev mode
+ *
+ * For development, the `transport` option can be replaced by `devMode: true`. Doing so configures Vendure to use the
+ * [file transport]({{}}) and outputs emails as rendered HTML files in a directory named "test-emails" which is located adjacent to the directory configured in the `templatePath`.
+ *
+ * ```ts
+ * new EmailPlugin({
+ *   templatePath: path.join(__dirname, 'vendure/email/templates'),
+ *   devMode: true,
+ * })
+ * ```
+ *
+ * @docsCategory EmailPlugin
  */
 export class EmailPlugin implements VendurePlugin {
     private readonly templatePath: string;

+ 17 - 15
packages/email-plugin/src/types.ts

@@ -5,16 +5,12 @@ import { ConfigService, VendureEvent } from '@vendure/core';
 import { EmailContext, GeneratedEmailContext } from './email-context';
 
 /**
- * @description
  * Defines how transactional emails (account verification, order confirmation etc) are generated and sent.
  *
  * {{% alert %}}
  * It is usually not recommended to configure these yourself.
  * You should use the `DefaultEmailPlugin`.
  * {{% /alert %}}
- *
- * @docsCategory email
- * @docsWeight 0
  */
 export interface EmailOptions<EmailType extends string> {
     /**
@@ -59,7 +55,7 @@ export interface EmailOptions<EmailType extends string> {
  * @description
  * Configuration for the EmailPlugin.
  *
- * @docsCategory plugin
+ * @docsCategory EmailPlugin
  */
 export interface EmailPluginOptions {
     /**
@@ -80,6 +76,12 @@ export interface EmailPluginOptions {
     templateVars: { [name: string]: any };
 }
 
+/**
+ * @description
+ * Configuration for running the EmailPlugin in development mode.
+ *
+ * @docsCategory EmailPlugin
+ */
 export interface EmailPluginDevModeOptions {
     templatePath: string;
     outputPath: string;
@@ -98,7 +100,7 @@ export interface SMTPCredentials {
  * @description
  * A subset of the SMTP transport options of [Nodemailer](https://nodemailer.com/smtp/)
  *
- * @docsCategory email
+ * @docsCategory EmailPlugin
  */
 export interface SMTPTransportOptions {
     type: 'smtp';
@@ -153,7 +155,7 @@ export interface SMTPTransportOptions {
  * @description
  * Uses the local Sendmail program to send the email.
  *
- * @docsCategory email
+ * @docsCategory EmailPlugin
  */
 export interface SendmailTransportOptions {
     type: 'sendmail';
@@ -167,7 +169,7 @@ export interface SendmailTransportOptions {
  * @description
  * Outputs the email as an HTML file for development purposes.
  *
- * @docsCategory email
+ * @docsCategory EmailPlugin
  */
 export interface FileTransportOptions {
     type: 'file';
@@ -181,7 +183,7 @@ export interface FileTransportOptions {
  * @description
  * Does nothing with the generated email. Mainly intended for use in testing where we don't care about the email transport.
  *
- * @docsCategory email
+ * @docsCategory EmailPlugin
  */
 export interface NoopTransportOptions {
     type: 'none';
@@ -191,7 +193,7 @@ export interface NoopTransportOptions {
  * @description
  * Forwards the raw GeneratedEmailContext object to a provided callback, for use in testing.
  *
- * @docsCategory email
+ * @docsCategory EmailPlugin
  */
 export interface TestingTransportOptions {
     type: 'testing';
@@ -206,7 +208,7 @@ export interface TestingTransportOptions {
  * @description
  * A union of all the possible transport options for sending emails.
  *
- * @docsCategory email
+ * @docsCategory EmailPlugin
  */
 export type EmailTransportOptions =
     | SMTPTransportOptions
@@ -220,7 +222,7 @@ export type EmailTransportOptions =
  * This object defines the template location and context data used for interpolation
  * of an email for a particular language of a particular channel.
  *
- * @docsCategory email
+ * @docsCategory EmailPlugin
  */
 export type TemplateConfig<C = any, R = any> = {
     /**
@@ -260,7 +262,7 @@ export type CreateContextResult = {
  * @description
  * An object which configures an particular type of transactional email.
  *
- * @docsCategory email
+ * @docsCategory EmailPlugin
  */
 export type EmailTypeConfig<T extends string, E extends VendureEvent = any> = {
     /**
@@ -327,7 +329,7 @@ export type EmailTypeConfig<T extends string, E extends VendureEvent = any> = {
  *  }),
  * ```
  *
- * @docsCategory email
+ * @docsCategory EmailPlugin
  */
 export type EmailTypes<T extends string> = { [emailType in T]: EmailTypeConfig<T> };
 
@@ -341,7 +343,7 @@ export function configEmailType<T extends string, E extends VendureEvent = Vendu
  * @description
  * The EmailGenerator uses the {@link EmailContext} and template to generate the email body
  *
- * @docsCategory email
+ * @docsCategory EmailPlugin
  */
 export interface EmailGenerator<T extends string = any, E extends VendureEvent = any> {
     /**

+ 21 - 9
scripts/docs/docgen-utils.ts

@@ -1,5 +1,6 @@
 import fs from 'fs';
 import klawSync from 'klaw-sync';
+import { basename } from 'path';
 // tslint:disable:no-console
 
 /**
@@ -7,7 +8,7 @@ import klawSync from 'klaw-sync';
  */
 export function generateFrontMatter(title: string, weight: number, showToc: boolean = true): string {
     return `---
-title: "${title.replace('-', ' ')}"
+title: "${title.replace(/-/g, ' ')}"
 weight: ${weight}
 date: ${new Date().toISOString()}
 showtoc: ${showToc}
@@ -21,16 +22,27 @@ generated: true
  * Delete all generated docs found in the outputPath.
  */
 export function deleteGeneratedDocs(outputPath: string) {
-    let deleteCount = 0;
-    const files = klawSync(outputPath, {nodir: true});
-    for (const file of files) {
-        const content = fs.readFileSync(file.path, 'utf-8');
-        if (isGenerated(content)) {
-            fs.unlinkSync(file.path);
-            deleteCount++;
+    if (!fs.existsSync(outputPath)) {
+        return;
+    }
+    try {
+        let deleteCount = 0;
+        const files = klawSync(outputPath, {nodir: true});
+        for (const file of files) {
+            const content = fs.readFileSync(file.path, 'utf-8');
+            if (isGenerated(content)) {
+                fs.unlinkSync(file.path);
+                deleteCount++;
+            }
+        }
+        if (deleteCount) {
+            console.log(`Deleted ${deleteCount} generated docs from ${outputPath}`);
         }
+    } catch (e) {
+        console.error('Could not delete generated docs!');
+        console.log(e);
+        process.exitCode = 1;
     }
-    console.log(`Deleted ${deleteCount} generated docs`);
 }
 
 /**

+ 63 - 32
scripts/docs/generate-typescript-docs.ts

@@ -1,36 +1,37 @@
 /* tslint:disable:no-console */
-import fs from 'fs';
 import klawSync from 'klaw-sync';
 import path from 'path';
-import ts from 'typescript';
 
 import { deleteGeneratedDocs } from './docgen-utils';
 import { TypeMap } from './typescript-docgen-types';
 import { TypescriptDocsParser } from './typescript-docs-parser';
 import { TypescriptDocsRenderer } from './typescript-docs-renderer';
 
-// The absolute URL to the generated docs section
-const DOCS_URL = '/docs/configuration/';
-// The directory in which the markdown files will be saved
-const OUTPUT_PATH = path.join(__dirname, '../../docs/content/docs/configuration');
-// The directories to scan for TypeScript source files
-const TS_SOURCE_DIRS = ['packages/core/src/', 'packages/common/src/'];
-
-const tsFiles = TS_SOURCE_DIRS
-    .map(scanPath =>
-        klawSync(path.join(__dirname, '../../', scanPath), {
-            nodir: true,
-            filter: item => path.extname(item.path) === '.ts',
-            traverseAll: true,
-        }),
-    )
-    .reduce((allFiles, files) => [...allFiles, ...files], [])
-    .map(item => item.path);
-
-deleteGeneratedDocs(OUTPUT_PATH);
-generateTypescriptDocs(tsFiles, OUTPUT_PATH, DOCS_URL);
-
-const watchMode = !!process.argv.find(arg => arg === '--watch' || arg === '-w');
+interface DocsSectionCofig {
+    sourceDirs: string[];
+    outputPath: string;
+}
+
+generateTypescriptDocs([
+    {
+        sourceDirs: ['packages/core/src/', 'packages/common/src/'],
+        outputPath: 'typescript-api',
+    },
+    {
+        sourceDirs: ['packages/asset-server-plugin/src/'],
+        outputPath: 'plugins',
+    },
+    {
+        sourceDirs: ['packages/email-plugin/src/'],
+        outputPath: 'plugins',
+    },
+    {
+        sourceDirs: ['packages/admin-ui-plugin/src/'],
+        outputPath: 'plugins',
+    },
+]);
+
+/*const watchMode = !!process.argv.find(arg => arg === '--watch' || arg === '-w');
 if (watchMode) {
     console.log(`Watching for changes to source files...`);
     tsFiles.forEach(file => {
@@ -38,26 +39,56 @@ if (watchMode) {
             generateTypescriptDocs([file], OUTPUT_PATH, DOCS_URL);
         });
     });
-}
+}*/
 
 /**
  * Uses the TypeScript compiler API to parse the given files and extract out the documentation
  * into markdown files
  */
-function generateTypescriptDocs(filePaths: string[], hugoOutputPath: string, docsUrl: string) {
+function generateTypescriptDocs(config: DocsSectionCofig[]) {
     const timeStart = +new Date();
 
     // This map is used to cache types and their corresponding Hugo path. It is used to enable
     // hyperlinking from a member's "type" to the definition of that type.
     const globalTypeMap: TypeMap = new Map();
 
-    const parsedDeclarations = new TypescriptDocsParser().parse(filePaths);
-    for (const info of parsedDeclarations) {
-        globalTypeMap.set(info.title, info.category + '/' + info.fileName);
+    for (const { outputPath, sourceDirs } of config) {
+        deleteGeneratedDocs(absOutputPath(outputPath));
     }
-    const generatedCount = new TypescriptDocsRenderer().render(parsedDeclarations, docsUrl, OUTPUT_PATH, globalTypeMap);
 
-    if (generatedCount) {
-        console.log(`Generated ${generatedCount} typescript api docs in ${+new Date() - timeStart}ms`);
+    for (const { outputPath, sourceDirs } of config) {
+        const sourceFilePaths = getSourceFilePaths(sourceDirs);
+        const parsedDeclarations = new TypescriptDocsParser().parse(sourceFilePaths);
+        for (const info of parsedDeclarations) {
+            globalTypeMap.set(info.title, info.category + '/' + info.fileName);
+        }
+        const docsUrl = `/docs/${outputPath}`;
+        const generatedCount = new TypescriptDocsRenderer().render(
+            parsedDeclarations,
+            docsUrl,
+            absOutputPath(outputPath),
+            globalTypeMap,
+        );
+
+        if (generatedCount) {
+            console.log(`Generated ${generatedCount} typescript api docs for "${outputPath}" in ${+new Date() - timeStart}ms`);
+        }
     }
 }
+
+function absOutputPath(outputPath: string): string {
+    return path.join(__dirname, '../../docs/content/docs/', outputPath);
+}
+
+function getSourceFilePaths(sourceDirs: string[]): string[] {
+    return sourceDirs
+        .map(scanPath =>
+            klawSync(path.join(__dirname, '../../', scanPath), {
+                nodir: true,
+                filter: item => path.extname(item.path) === '.ts',
+                traverseAll: true,
+            }),
+        )
+        .reduce((allFiles, files) => [...allFiles, ...files], [])
+        .map(item => item.path);
+}

+ 11 - 6
scripts/docs/typescript-docs-parser.ts

@@ -85,10 +85,8 @@ export class TypescriptDocsParser {
         const fullText = this.getDeclarationFullText(statement);
         const weight = this.getDeclarationWeight(statement);
         const description = this.getDeclarationDescription(statement);
-        const fileName = title
-            .split(/(?=[A-Z])/)
-            .join('-')
-            .toLowerCase();
+        const normalizedTitle = this.kebabCase(title);
+        const fileName = normalizedTitle === category ? '_index' : normalizedTitle;
 
         const info = {
             sourceFile,
@@ -260,8 +258,8 @@ export class TypescriptDocsParser {
         this.parseTags(statement, {
             docsCategory: tag => (category = tag.comment || ''),
         });
-        return category;
-    }
+        return this.kebabCase(category);
+    };
 
     /**
      * Type guard for the types of statement which can ge processed by the doc generator.
@@ -298,4 +296,11 @@ export class TypescriptDocsParser {
         return '\n\n*Example*\n\n' + example.replace(/\n\s+\*\s/g, '\n');
     }
 
+    private kebabCase<T extends string | undefined>(input: T): T {
+        if (input == null) {
+            return input;
+        }
+        return input.replace(/([a-z])([A-Z])/g, '$1-$2').replace(/\s+/g, '-').toLowerCase() as T;
+    }
+
 }

+ 5 - 2
scripts/docs/typescript-docs-renderer.ts

@@ -1,5 +1,5 @@
 // tslint:disable:no-console
-import fs from 'fs';
+import fs from 'fs-extra';
 import klawSync from 'klaw-sync';
 import path from 'path';
 import ts from 'typescript';
@@ -13,6 +13,9 @@ export class TypescriptDocsRenderer {
 
     render(parsedDeclarations: ParsedDeclaration[], docsUrl: string, outputPath: string, typeMap: TypeMap): number {
         let generatedCount = 0;
+        if (!fs.existsSync(outputPath)) {
+            fs.mkdirs(outputPath);
+        }
         for (const info of parsedDeclarations) {
             let markdown = '';
             switch (info.kind) {
@@ -32,7 +35,7 @@ export class TypescriptDocsRenderer {
             const categoryDir = path.join(outputPath, info.category);
             const indexFile = path.join(categoryDir, '_index.md');
             if (!fs.existsSync(categoryDir)) {
-                fs.mkdirSync(categoryDir);
+                fs.mkdirs(categoryDir);
             }
             if (!fs.existsSync(indexFile)) {
                 const indexFileContent = generateFrontMatter(info.category, 10, false) + `\n\n# ${info.category}`;