From ccb48fdf3cf85f76657b4205e85aaeb7288bc6c0 Mon Sep 17 00:00:00 2001
From: Emerson Jair <emerson@zoocha.com>
Date: Sun, 17 Mar 2019 03:28:50 -0300
Subject: [PATCH] feat(generators): add angularJson option to app.nest

Add the project to angular.json along with a couple of targets.
It still misses the test target
close #94
---
 .../{tsconfig.json => tsconfig__ext__.json}   |  0
 src/app.nest/index.ts                         | 73 ++++++++++++++++---
 src/app.nest/index_spec.ts                    | 55 +++++++++++++-
 src/app.nest/schema.d.ts                      |  4 +
 src/app.nest/schema.json                      |  5 ++
 src/utils/general.ts                          | 15 +++-
 6 files changed, 138 insertions(+), 14 deletions(-)
 rename src/app.nest/_files/{tsconfig.json => tsconfig__ext__.json} (100%)

diff --git a/src/app.nest/_files/tsconfig.json b/src/app.nest/_files/tsconfig__ext__.json
similarity index 100%
rename from src/app.nest/_files/tsconfig.json
rename to src/app.nest/_files/tsconfig__ext__.json
diff --git a/src/app.nest/index.ts b/src/app.nest/index.ts
index dd93eba3..2937aa91 100644
--- a/src/app.nest/index.ts
+++ b/src/app.nest/index.ts
@@ -11,7 +11,8 @@ import {
   template,
   move,
   Rule,
-  noop
+  noop,
+  externalSchematic
 } from "@angular-devkit/schematics";
 import { NodePackageInstallTask } from "@angular-devkit/schematics/tasks";
 import {
@@ -27,10 +28,11 @@ import {
   prerun,
   applyAppNamingConvention,
   getAppName,
-  missingNameArgument
+  missingNameArgument,
+  updateJsonInTree
 } from "../utils";
 
-export default function(options: ApplicationOptions) {
+export default function (options: ApplicationOptions) {
   if (!options.name) {
     throw new SchematicsException(
       missingNameArgument('Provide a name for your Nest app.', 'ng g app.nest sample')
@@ -43,6 +45,7 @@ export default function(options: ApplicationOptions) {
     prerun(options),
     // adjust naming convention
     applyAppNamingConvention(options, 'nest'),
+    options.angularJson ? (tree: Tree) => updateAngularJson(options, tree) : noop(),
     // create app files
     (tree: Tree, context: SchematicContext) =>
       addAppFiles(options, appPath)(tree, context),
@@ -53,30 +56,31 @@ export default function(options: ApplicationOptions) {
       const platformApp = options.name.replace('-', '.');
       const packageConfig = getJsonFromFile(tree, "package.json");
       const scripts = packageConfig.scripts || {};
+      const tsConfig = `tsconfig${options.angularJson ? ".app" : ""}.json`;
 
       scripts[`serve.${platformApp}`] = `ts-node -P apps/${
         options.name
-      }/tsconfig.json apps/${options.name}/src/main.ts`;
+        }/${tsConfig} apps/${options.name}/src/main.ts`;
       scripts[`start.${platformApp}`] = `npm-run-all -p serve.${
         platformApp
-      }`;
+        }`;
       scripts[`build.${platformApp}`] = `tsc -p apps/${
         options.name
-      }`;
+        }/${tsConfig}`;
       scripts[`test.${platformApp}`] = `jest --config=apps/${
         options.name
-      }/jest.json`;
+        }/jest.json`;
       scripts[
         `test.${platformApp}.coverage`
       ] = `jest --config=apps/${
         options.name
-      }/jest.json --coverage --coverageDirectory=coverage`;
+        }/jest.json --coverage --coverageDirectory=coverage`;
       scripts[`test.${platformApp}.watch`] = `jest --config=apps/${
         options.name
-      }/jest.json --watch`;
+        }/jest.json --watch`;
       scripts[`test.${platformApp}.e2e`] = `jest --config=apps/${
         options.name
-      }/e2e/jest-e2e.json --forceExit`;
+        }/e2e/jest-e2e.json --forceExit`;
       scripts[
         `test.${platformApp}.e2e.watch`
       ] = `jest --config=apps/${options.name}/e2e/jest-e2e.json --watch`;
@@ -118,10 +122,57 @@ function addAppFiles(
           utils: stringUtils,
           npmScope: getNpmScope(),
           prefix: getPrefix(),
-          dot: "."
+          dot: ".",
+          ext: options.angularJson ? ".app" : ""
         }),
         move(`apps/${appPath}`)
       ])
     )
   );
 }
+
+type AngularProject = { architect: { build: { options: { assets: any[] } }, test: {}, lint: { options: { tsConfig: string[] } } } }
+
+/**
+ * Remove assets option (not created), test target because it does not work
+ * and spec tsConfig.
+ * @todo fix the test target
+ * @param nestProject Project configuration from angular.json
+ */
+function tweakNxNestArchitect(nestProject: AngularProject) {
+  delete nestProject.architect.build.options.assets;
+  delete nestProject.architect.test;
+  nestProject.architect.lint.options.tsConfig.pop();
+
+  return nestProject;
+}
+
+/**
+ * Add Nest project to angular.json along with the architects targets
+ * @param options 
+ * @param host 
+ */
+function updateAngularJson(options: ApplicationOptions, host: Tree): Rule {
+  let nestProject: AngularProject;
+
+  return chain(
+    [
+      externalSchematic("@nrwl/schematics", "node-application", {
+        ...options,
+        skipInstall: true,
+        skipFormat: true,
+        skipPackageJson: true,
+        framework: "nestjs"
+      }),
+      (tree: Tree) => {
+        const nxAngular: { projects: {} } = JSON.parse(tree.read("/angular.json").toString());
+        nestProject = tweakNxNestArchitect(nxAngular.projects[options.name]);
+        return host;
+      },
+      updateJsonInTree("angular.json", angularJson => {
+        angularJson.projects[options.name] = nestProject;
+        return angularJson;
+      })
+    ]
+  );
+}
diff --git a/src/app.nest/index_spec.ts b/src/app.nest/index_spec.ts
index b106cb51..c7076ffa 100644
--- a/src/app.nest/index_spec.ts
+++ b/src/app.nest/index_spec.ts
@@ -3,7 +3,7 @@ import { Schema as ApplicationOptions } from "./schema";
 import { SchematicTestRunner } from "@angular-devkit/schematics/testing";
 
 import * as path from "path";
-import { createEmptyWorkspace, getFileContent, jsonParse } from "../utils";
+import { createEmptyWorkspace, getFileContent, jsonParse, getPlatformName } from "../utils";
 
 describe("app.nest schematic", () => {
   const schematicRunner = new SchematicTestRunner(
@@ -62,4 +62,57 @@ describe("app.nest schematic", () => {
     expect(packageData.scripts["serve.nest.foo"]).toBeDefined();
     expect(packageData.scripts["start.nest.foo"]).toBeDefined();
   });
+
+  it("should update angular.json file if angularJson is true", () => {
+    const options: ApplicationOptions = { ...defaultOptions, angularJson: true };
+    const tree = schematicRunner.runSchematic("app.nest", options, appTree);
+    const files = tree.files;
+    const projectName = getPlatformName(options.name, "nest");
+
+    expect(
+      files.indexOf(`/apps/${projectName}/src/main.ts`)
+    ).toBeGreaterThanOrEqual(0);
+    expect(
+      files.indexOf(`/apps/${projectName}/src/app.service.ts`)
+    ).toBeGreaterThanOrEqual(0);
+    expect(
+      files.indexOf(`/apps/${projectName}/src/app.module.ts`)
+    ).toBeGreaterThanOrEqual(0);
+    expect(
+      files.indexOf(`/apps/${projectName}/src/app.controller.ts`)
+    ).toBeGreaterThanOrEqual(0);
+
+    let checkPath = `/apps/${projectName}/package.json`;
+    expect(files.indexOf(checkPath)).toBeGreaterThanOrEqual(0);
+
+    let checkFile = getFileContent(tree, checkPath);
+    expect(checkFile.indexOf(`"name": "foo"`)).toBeGreaterThanOrEqual(0);
+
+    expect(
+      files.indexOf("/tools/electron/postinstall.js")
+    ).toBeGreaterThanOrEqual(0);
+    expect(files.indexOf("/tools/web/postinstall.js")).toBeGreaterThanOrEqual(
+      0
+    );
+
+    checkPath = "/package.json";
+    expect(files.indexOf(checkPath)).toBeGreaterThanOrEqual(0);
+
+    checkFile = getFileContent(tree, checkPath);
+
+    const packageData: any = jsonParse(checkFile);
+    expect(packageData.scripts["serve.nest.foo"]).toBeDefined();
+    expect(packageData.scripts["start.nest.foo"]).toBeDefined();
+
+    expect(
+      files.indexOf(`/apps/${projectName}/tsconfig.app.json`)
+    ).toBeGreaterThanOrEqual(0);
+
+    checkPath = "/angular.json";
+    expect(files.indexOf(checkPath)).toBeGreaterThanOrEqual(0);
+
+    checkFile = getFileContent(tree, checkPath);
+    const angularData: { projects: {} } = jsonParse(checkFile);
+    expect(angularData.projects[projectName]).toBeDefined();
+  })
 });
diff --git a/src/app.nest/schema.d.ts b/src/app.nest/schema.d.ts
index 7cbcd10b..b5a27635 100644
--- a/src/app.nest/schema.d.ts
+++ b/src/app.nest/schema.d.ts
@@ -19,4 +19,8 @@ export interface Schema {
    * Skip formatting files
    */
   skipFormat?: boolean;
+  /**
+   * Add project to angular.json
+   */
+  angularJson?: boolean;
 }
diff --git a/src/app.nest/schema.json b/src/app.nest/schema.json
index bee83b7d..95b2ed2f 100644
--- a/src/app.nest/schema.json
+++ b/src/app.nest/schema.json
@@ -37,6 +37,11 @@
       "description": "Skip formatting files",
       "type": "boolean",
       "default": false
+    },
+    "angularJson": {
+      "description": "Add project to angular.json",
+      "type": "boolean",
+      "default": false
     }
   },
   "required": []
diff --git a/src/utils/general.ts b/src/utils/general.ts
index 01e065fa..57d52836 100644
--- a/src/utils/general.ts
+++ b/src/utils/general.ts
@@ -165,10 +165,21 @@ export function getNxWorkspaceConfig(tree: Tree): any {
   );
 }
 
+/**
+ * Returns a name with the platform.
+ * 
+ * @example (app, nest) => web-app or app-web
+ * @param name 
+ * @param platform 
+ */
+export function getPlatformName(name: string, platform: PlatformTypes) {
+  const nameSanitized = toFileName(name);
+  return groupByName ? `${nameSanitized}-${platform}` : `${platform}-${nameSanitized}`;
+}
+
 export function applyAppNamingConvention(options: any, platform: PlatformTypes) {
   return (tree: Tree, context: SchematicContext) => {
-    const nameSanitized = toFileName(options.name);
-    options.name = groupByName ? `${nameSanitized}-${platform}` : `${platform}-${nameSanitized}`;
+    options.name = getPlatformName(options.name, platform);
     // if command line argument, make sure it's persisted to xplat settings
     if (options.groupByName) {
       return updatePackageForXplat(tree, null, {