diff --git a/e2e/workspace/src/create-nx-workspace.test.ts b/e2e/workspace/src/create-nx-workspace.test.ts index 9c0ec4d917..26cb1bf2dc 100644 --- a/e2e/workspace/src/create-nx-workspace.test.ts +++ b/e2e/workspace/src/create-nx-workspace.test.ts @@ -119,6 +119,8 @@ describe('create-nx-workspace', () => { style: 'css', appName, }); + + expectNoAngularDevkit(); }); it('should be able to create an express workspace', () => { @@ -129,6 +131,8 @@ describe('create-nx-workspace', () => { style: 'css', appName, }); + + expectNoAngularDevkit(); }); it('should be able to create a workspace with a custom base branch and HEAD', () => { diff --git a/packages/express/.eslintrc.json b/packages/express/.eslintrc.json index 9a75f74f8b..ce862921c1 100644 --- a/packages/express/.eslintrc.json +++ b/packages/express/.eslintrc.json @@ -17,6 +17,19 @@ { "files": ["*.js", "*.jsx"], "rules": {} + }, + { + "files": ["**/*.ts"], + "excludedFiles": ["./src/migrations/**"], + "rules": { + "no-restricted-imports": [ + "error", + "@nrwl/workspace", + "@angular-devkit/core", + "@angular-devkit/architect", + "@angular-devkit/schematics" + ] + } } ] } diff --git a/packages/express/collection.json b/packages/express/collection.json index 6046c36f05..f2867f194d 100644 --- a/packages/express/collection.json +++ b/packages/express/collection.json @@ -2,18 +2,34 @@ "name": "Nx Express", "version": "0.1", "extends": ["@nrwl/workspace"], - "schematics": { + "generators": { "init": { - "factory": "./src/schematics/init/init", - "schema": "./src/schematics/init/schema.json", + "factory": "./src/generators/init/init#initGenerator", + "schema": "./src/generators/init/schema.json", "description": "Initialize the @nrwl/express plugin", "alias": ["ng-add"], "hidden": true }, "application": { - "factory": "./src/schematics/application/application", - "schema": "./src/schematics/application/schema.json", + "factory": "./src/generators/application/application#applicationGenerator", + "schema": "./src/generators/application/schema.json", + "aliases": ["app"], + "description": "Create an express application" + } + }, + "schematics": { + "init": { + "factory": "./src/generators/init/init#initSchematic", + "schema": "./src/generators/init/schema.json", + "description": "Initialize the @nrwl/express plugin", + "alias": ["ng-add"], + "hidden": true + }, + + "application": { + "factory": "./src/generators/application/application#applicationSchematic", + "schema": "./src/generators/application/schema.json", "aliases": ["app"], "description": "Create an express application" } diff --git a/packages/express/index.ts b/packages/express/index.ts index 47d712ab8e..69032cd12b 100644 --- a/packages/express/index.ts +++ b/packages/express/index.ts @@ -1 +1 @@ -export { applicationGenerator } from './src/schematics/application/application'; +export { applicationGenerator } from './src/generators/application/application'; diff --git a/packages/express/package.json b/packages/express/package.json index cb5d12a73c..72d00f4730 100644 --- a/packages/express/package.json +++ b/packages/express/package.json @@ -32,7 +32,6 @@ "@nrwl/devkit": "*", "@nrwl/node": "*", "@nrwl/jest": "*", - "@angular-devkit/core": "~11.2.0", - "@angular-devkit/schematics": "~11.2.0" + "@nrwl/workspace": "*" } } diff --git a/packages/express/src/schematics/application/application.spec.ts b/packages/express/src/generators/application/application.spec.ts similarity index 65% rename from packages/express/src/schematics/application/application.spec.ts rename to packages/express/src/generators/application/application.spec.ts index 4a0ec7d090..045558be7a 100644 --- a/packages/express/src/schematics/application/application.spec.ts +++ b/packages/express/src/generators/application/application.spec.ts @@ -1,29 +1,25 @@ -import { Tree } from '@angular-devkit/schematics'; -import { createEmptyWorkspace } from '@nrwl/workspace/testing'; -import { runSchematic } from '../../utils/testing'; -import { readJsonInTree } from '@nrwl/workspace'; +import { readJson, Tree } from '@nrwl/devkit'; +import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; -import { Schema } from './schema.d'; +import { applicationGenerator } from './application'; +import { Schema } from './schema'; describe('app', () => { let appTree: Tree; beforeEach(() => { - appTree = Tree.empty(); - appTree = createEmptyWorkspace(appTree); + appTree = createTreeWithEmptyWorkspace(); }); it('should generate files', async () => { - const tree = await runSchematic( - 'app', - { name: 'myNodeApp' } as Schema, - appTree - ); + await applicationGenerator(appTree, { + name: 'myNodeApp', + } as Schema); - const mainFile = tree.readContent('apps/my-node-app/src/main.ts'); + const mainFile = appTree.read('apps/my-node-app/src/main.ts').toString(); expect(mainFile).toContain(`import * as express from 'express';`); - const tsconfig = readJsonInTree(tree, 'apps/my-node-app/tsconfig.json'); + const tsconfig = readJson(appTree, 'apps/my-node-app/tsconfig.json'); expect(tsconfig).toMatchInlineSnapshot(` Object { "extends": "../../tsconfig.base.json", @@ -40,10 +36,7 @@ describe('app', () => { } `); - const eslintrcJson = readJsonInTree( - tree, - 'apps/my-node-app/.eslintrc.json' - ); + const eslintrcJson = readJson(appTree, 'apps/my-node-app/.eslintrc.json'); expect(eslintrcJson).toMatchInlineSnapshot(` Object { "extends": Array [ @@ -87,12 +80,10 @@ describe('app', () => { }); it('should add types to the tsconfig.app.json', async () => { - const tree = await runSchematic( - 'app', - { name: 'myNodeApp' } as Schema, - appTree - ); - const tsconfig = readJsonInTree(tree, 'apps/my-node-app/tsconfig.app.json'); + await applicationGenerator(appTree, { + name: 'myNodeApp', + } as Schema); + const tsconfig = readJson(appTree, 'apps/my-node-app/tsconfig.app.json'); expect(tsconfig.compilerOptions.types).toContain('express'); expect(tsconfig).toMatchInlineSnapshot(` Object { @@ -117,27 +108,23 @@ describe('app', () => { describe('--js flag', () => { it('should generate js files instead of ts files', async () => { - const tree = await runSchematic( - 'app', - { - name: 'myNodeApp', - js: true, - } as Schema, - appTree - ); + await applicationGenerator(appTree, { + name: 'myNodeApp', + js: true, + } as Schema); - expect(tree.exists('apps/my-node-app/src/main.js')).toBeTruthy(); - expect(tree.readContent('apps/my-node-app/src/main.js')).toContain( + expect(appTree.exists('apps/my-node-app/src/main.js')).toBeTruthy(); + expect(appTree.read('apps/my-node-app/src/main.js').toString()).toContain( `import * as express from 'express';` ); - const tsConfig = readJsonInTree(tree, 'apps/my-node-app/tsconfig.json'); + const tsConfig = readJson(appTree, 'apps/my-node-app/tsconfig.json'); expect(tsConfig.compilerOptions).toEqual({ allowJs: true, }); - const tsConfigApp = readJsonInTree( - tree, + const tsConfigApp = readJson( + appTree, 'apps/my-node-app/tsconfig.app.json' ); expect(tsConfigApp.include).toEqual(['**/*.ts', '**/*.js']); diff --git a/packages/express/src/generators/application/application.ts b/packages/express/src/generators/application/application.ts new file mode 100644 index 0000000000..e5e60a1090 --- /dev/null +++ b/packages/express/src/generators/application/application.ts @@ -0,0 +1,90 @@ +import { + convertNxGenerator, + formatFiles, + getWorkspaceLayout, + joinPathFragments, + names, + toJS, + Tree, + updateJson, +} from '@nrwl/devkit'; + +import { applicationGenerator as nodeApplicationGenerator } from '@nrwl/node'; + +import { join } from 'path'; +import { Schema } from './schema'; +import { initGenerator } from '../init/init'; + +interface NormalizedSchema extends Schema { + appProjectRoot: string; +} + +function addTypes(tree: Tree, options: NormalizedSchema) { + const tsConfigPath = join(options.appProjectRoot, 'tsconfig.app.json'); + updateJson(tree, tsConfigPath, (json) => { + json.compilerOptions.types = [...json.compilerOptions.types, 'express']; + return json; + }); +} + +function addAppFiles(tree: Tree, options: NormalizedSchema) { + tree.write( + join(options.appProjectRoot, `src/main.${options.js ? 'js' : 'ts'}`), + `/** + * This is not a production server yet! + * This is only a minimal backend to get started. + */ + +import * as express from 'express'; + +const app = express(); + +app.get('/api', (req, res) => { + res.send({ message: 'Welcome to ${options.name}!' }); +}); + +const port = process.env.port || 3333; +const server = app.listen(port, () => { + console.log(\`Listening at http://localhost:\${port}/api\`); +}); +server.on('error', console.error); +` + ); + + if (options.js) { + toJS(tree); + } +} + +export async function applicationGenerator(tree: Tree, schema: Schema) { + const options = normalizeOptions(tree, schema); + const initTask = await initGenerator(tree, { ...options, skipFormat: true }); + const applicationTask = await nodeApplicationGenerator(tree, { + ...schema, + skipFormat: true, + }); + addAppFiles(tree, options); + addTypes(tree, options); + await formatFiles(tree); + + return async () => { + await initTask(); + await applicationTask(); + }; +} + +export default applicationGenerator; +export const applicationSchematic = convertNxGenerator(applicationGenerator); + +function normalizeOptions(host: Tree, options: Schema): NormalizedSchema { + const appDirectory = options.directory + ? `${names(options.directory).fileName}/${names(options.name).fileName}` + : names(options.name).fileName; + const { appsDir } = getWorkspaceLayout(host); + const appProjectRoot = joinPathFragments(appsDir, appDirectory); + + return { + ...options, + appProjectRoot, + }; +} diff --git a/packages/express/src/schematics/application/schema.d.ts b/packages/express/src/generators/application/schema.d.ts similarity index 88% rename from packages/express/src/schematics/application/schema.d.ts rename to packages/express/src/generators/application/schema.d.ts index 6b17559453..abbaca1a6a 100644 --- a/packages/express/src/schematics/application/schema.d.ts +++ b/packages/express/src/generators/application/schema.d.ts @@ -1,5 +1,5 @@ import { UnitTestRunner } from '../../utils/test-runners'; -import { Linter } from '@nrwl/workspace'; +import type { Linter } from '@nrwl/linter'; export interface Schema { name: string; diff --git a/packages/express/src/schematics/application/schema.json b/packages/express/src/generators/application/schema.json similarity index 99% rename from packages/express/src/schematics/application/schema.json rename to packages/express/src/generators/application/schema.json index 45b358b591..be0917061c 100644 --- a/packages/express/src/schematics/application/schema.json +++ b/packages/express/src/generators/application/schema.json @@ -1,5 +1,6 @@ { "$schema": "http://json-schema.org/schema", + "cli": "nx", "id": "SchematicsNxExpressApp", "title": "Nx Application Options Schema", "type": "object", diff --git a/packages/express/src/schematics/init/init.spec.ts b/packages/express/src/generators/init/init.spec.ts similarity index 52% rename from packages/express/src/schematics/init/init.spec.ts rename to packages/express/src/generators/init/init.spec.ts index 1212570482..d8a775ffd1 100644 --- a/packages/express/src/schematics/init/init.spec.ts +++ b/packages/express/src/generators/init/init.spec.ts @@ -1,30 +1,26 @@ -import { Tree } from '@angular-devkit/schematics'; -import { addDepsToPackageJson, readJsonInTree } from '@nrwl/workspace'; -import { createEmptyWorkspace } from '@nrwl/workspace/testing'; -import { runSchematic, callRule } from '../../utils/testing'; +import { addDependenciesToPackageJson, Tree } from '@nrwl/devkit'; import { expressVersion } from '../../utils/versions'; +import initGenerator from './init'; +import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; +import { readJson } from '@nrwl/devkit'; describe('init', () => { let tree: Tree; beforeEach(() => { - tree = Tree.empty(); - tree = createEmptyWorkspace(tree); + tree = createTreeWithEmptyWorkspace(); }); it('should add dependencies', async () => { const existing = 'existing'; const existingVersion = '1.0.0'; - await callRule( - addDepsToPackageJson( - { '@nrwl/express': expressVersion, [existing]: existingVersion }, - { [existing]: existingVersion }, - false - ), - tree + addDependenciesToPackageJson( + tree, + { '@nrwl/express': expressVersion, [existing]: existingVersion }, + { [existing]: existingVersion } ); - const result = await runSchematic('init', {}, tree); - const packageJson = readJsonInTree(result, 'package.json'); + await initGenerator(tree, {}); + const packageJson = readJson(tree, 'package.json'); // add express expect(packageJson.dependencies['express']).toBeDefined(); // move `@nrwl/express` to dev @@ -39,20 +35,16 @@ describe('init', () => { describe('defaultCollection', () => { it('should be set if none was set before', async () => { - const result = await runSchematic('init', {}, tree); - const workspaceJson = readJsonInTree(result, 'workspace.json'); + await initGenerator(tree, {}); + const workspaceJson = readJson(tree, 'workspace.json'); expect(workspaceJson.cli.defaultCollection).toEqual('@nrwl/express'); }); }); it('should not add jest config if unitTestRunner is none', async () => { - const result = await runSchematic( - 'init', - { - unitTestRunner: 'none', - }, - tree - ); - expect(result.exists('jest.config.js')).toEqual(false); + await initGenerator(tree, { + unitTestRunner: 'none', + }); + expect(tree.exists('jest.config.js')).toEqual(false); }); }); diff --git a/packages/express/src/generators/init/init.ts b/packages/express/src/generators/init/init.ts new file mode 100644 index 0000000000..c8a0a02023 --- /dev/null +++ b/packages/express/src/generators/init/init.ts @@ -0,0 +1,57 @@ +import { + addDependenciesToPackageJson, + Tree, + updateJson, + formatFiles, + convertNxGenerator, +} from '@nrwl/devkit'; + +import { setDefaultCollection } from '@nrwl/workspace/src/utilities/set-default-collection'; + +import { initGenerator as nodeInitGenerator } from '@nrwl/node'; + +import { + expressTypingsVersion, + expressVersion, + nxVersion, +} from '../../utils/versions'; +import { Schema } from './schema'; + +function removeNrwlExpressFromDeps(tree: Tree) { + updateJson(tree, 'package.json', (json) => { + delete json.dependencies['@nrwl/express']; + return json; + }); +} + +export async function initGenerator(tree: Tree, schema: Schema) { + setDefaultCollection(tree, '@nrwl/express'); + + const initTask = await nodeInitGenerator(tree, { + unitTestRunner: schema.unitTestRunner, + skipFormat: true, + }); + removeNrwlExpressFromDeps(tree); + const installTask = addDependenciesToPackageJson( + tree, + { + express: expressVersion, + tslib: '^2.0.0', + }, + { + '@types/express': expressTypingsVersion, + '@nrwl/express': nxVersion, + } + ); + if (!schema.skipFormat) { + await formatFiles(tree); + } + + return async () => { + await initTask(); + await installTask(); + }; +} + +export default initGenerator; +export const initSchematic = convertNxGenerator(initGenerator); diff --git a/packages/express/src/generators/init/schema.d.ts b/packages/express/src/generators/init/schema.d.ts new file mode 100644 index 0000000000..dde6ab2877 --- /dev/null +++ b/packages/express/src/generators/init/schema.d.ts @@ -0,0 +1,4 @@ +export interface Schema { + unitTestRunner?: 'jest' | 'none'; + skipFormat?: boolean; +} diff --git a/packages/express/src/schematics/init/schema.json b/packages/express/src/generators/init/schema.json similarity index 96% rename from packages/express/src/schematics/init/schema.json rename to packages/express/src/generators/init/schema.json index 25bf9b2039..449586e836 100644 --- a/packages/express/src/schematics/init/schema.json +++ b/packages/express/src/generators/init/schema.json @@ -1,5 +1,6 @@ { "$schema": "http://json-schema.org/schema", + "cli": "nx", "id": "NxExpressInit", "title": "Init Express Plugin", "type": "object", diff --git a/packages/express/src/schematics/application/application.ts b/packages/express/src/schematics/application/application.ts deleted file mode 100644 index 6e034ed4d8..0000000000 --- a/packages/express/src/schematics/application/application.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { - chain, - externalSchematic, - Rule, - SchematicContext, - Tree, -} from '@angular-devkit/schematics'; -import { join, normalize, Path } from '@angular-devkit/core'; -import { Schema } from './schema'; -import { updateJsonInTree } from '@nrwl/workspace'; -import { formatFiles } from '@nrwl/workspace'; -import init from '../init/init'; -import { appsDir } from '@nrwl/workspace/src/utils/ast-utils'; -import { maybeJs } from '@nrwl/workspace/src/utils/rules/to-js'; -import { names } from '@nrwl/devkit'; -import { wrapAngularDevkitSchematic } from '@nrwl/devkit/ngcli-adapter'; - -interface NormalizedSchema extends Schema { - appProjectRoot: Path; -} - -function addTypes(options: NormalizedSchema): Rule { - const tsConfigPath = join(options.appProjectRoot, 'tsconfig.app.json'); - return updateJsonInTree(tsConfigPath, (json) => { - json.compilerOptions.types = [...json.compilerOptions.types, 'express']; - return json; - }); -} - -function addAppFiles(options: NormalizedSchema): Rule { - return (host: Tree) => { - host.overwrite( - maybeJs(options, join(options.appProjectRoot, 'src/main.ts')), - `/** - * This is not a production server yet! - * This is only a minimal backend to get started. - */ - -import * as express from 'express'; - -const app = express(); - -app.get('/api', (req, res) => { - res.send({ message: 'Welcome to ${options.name}!' }); -}); - -const port = process.env.port || 3333; -const server = app.listen(port, () => { - console.log(\`Listening at http://localhost:\${port}/api\`); -}); -server.on('error', console.error); -` - ); - }; -} - -export default function (schema: Schema): Rule { - return (host: Tree, context: SchematicContext) => { - const options = normalizeOptions(host, schema); - return chain([ - init({ ...options, skipFormat: true }), - externalSchematic('@nrwl/node', 'application', schema), - addAppFiles(options), - addTypes(options), - formatFiles(options), - ])(host, context); - }; -} - -function normalizeOptions(host: Tree, options: Schema): NormalizedSchema { - const appDirectory = options.directory - ? `${names(options.directory).fileName}/${names(options.name).fileName}` - : names(options.name).fileName; - const appProjectRoot = join(normalize(appsDir(host)), appDirectory); - - return { - ...options, - appProjectRoot, - }; -} - -export const applicationGenerator = wrapAngularDevkitSchematic( - '@nrwl/express', - 'application' -); diff --git a/packages/express/src/schematics/init/init.ts b/packages/express/src/schematics/init/init.ts deleted file mode 100644 index 516acbb477..0000000000 --- a/packages/express/src/schematics/init/init.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { chain, noop, Rule } from '@angular-devkit/schematics'; -import { - addPackageWithInit, - formatFiles, - setDefaultCollection, - addDepsToPackageJson, - updateJsonInTree, -} from '@nrwl/workspace'; -import { - expressTypingsVersion, - expressVersion, - nxVersion, -} from '../../utils/versions'; -import { Schema } from './schema'; - -function removeNrwlExpressFromDeps(): Rule { - return updateJsonInTree('package.json', (json) => { - delete json.dependencies['@nrwl/express']; - return json; - }); -} - -export default function (schema: Schema) { - return chain([ - setDefaultCollection('@nrwl/express'), - addPackageWithInit('@nrwl/node', schema), - schema.unitTestRunner === 'jest' - ? addPackageWithInit('@nrwl/jest') - : noop(), - removeNrwlExpressFromDeps(), - addDepsToPackageJson( - { - express: expressVersion, - tslib: '^2.0.0', - }, - { - '@types/express': expressTypingsVersion, - '@nrwl/express': nxVersion, - } - ), - formatFiles(schema), - ]); -} diff --git a/packages/express/src/schematics/init/schema.d.ts b/packages/express/src/schematics/init/schema.d.ts deleted file mode 100644 index 52ac13000a..0000000000 --- a/packages/express/src/schematics/init/schema.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface Schema { - unitTestRunner: 'jest' | 'none'; - skipFormat: boolean; -} diff --git a/packages/express/src/utils/testing.ts b/packages/express/src/utils/testing.ts deleted file mode 100644 index 2d09523774..0000000000 --- a/packages/express/src/utils/testing.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { join } from 'path'; -import { SchematicTestRunner } from '@angular-devkit/schematics/testing'; -import { Rule, Tree } from '@angular-devkit/schematics'; - -const testRunner = new SchematicTestRunner( - '@nrwl/express', - join(__dirname, '../../collection.json') -); - -testRunner.registerCollection( - '@nrwl/jest', - join(__dirname, '../../../jest/collection.json') -); - -testRunner.registerCollection( - '@nrwl/node', - join(__dirname, '../../../node/collection.json') -); - -export function runSchematic(schematicName: string, options: any, tree: Tree) { - return testRunner.runSchematicAsync(schematicName, options, tree).toPromise(); -} - -export function callRule(rule: Rule, tree: Tree) { - return testRunner.callRule(rule, tree).toPromise(); -} diff --git a/packages/node/index.ts b/packages/node/index.ts index 2c89d6a04f..642dd5b126 100644 --- a/packages/node/index.ts +++ b/packages/node/index.ts @@ -1,2 +1,3 @@ export { applicationGenerator } from './src/generators/application/application'; export { libraryGenerator } from './src/generators/library/library'; +export { initGenerator } from './src/generators/init/init';