feat(node): migrate @nrwl/express to use devkit (#5558)

This commit is contained in:
Jason Jean 2021-05-04 18:48:45 -04:00 committed by GitHub
parent c9e79ab84e
commit 6503c4fa03
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 236 additions and 229 deletions

View File

@ -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', () => {

View File

@ -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"
]
}
}
]
}

View File

@ -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"
}

View File

@ -1 +1 @@
export { applicationGenerator } from './src/schematics/application/application';
export { applicationGenerator } from './src/generators/application/application';

View File

@ -32,7 +32,6 @@
"@nrwl/devkit": "*",
"@nrwl/node": "*",
"@nrwl/jest": "*",
"@angular-devkit/core": "~11.2.0",
"@angular-devkit/schematics": "~11.2.0"
"@nrwl/workspace": "*"
}
}

View File

@ -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']);

View File

@ -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,
};
}

View File

@ -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;

View File

@ -1,5 +1,6 @@
{
"$schema": "http://json-schema.org/schema",
"cli": "nx",
"id": "SchematicsNxExpressApp",
"title": "Nx Application Options Schema",
"type": "object",

View File

@ -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);
});
});

View File

@ -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);

View File

@ -0,0 +1,4 @@
export interface Schema {
unitTestRunner?: 'jest' | 'none';
skipFormat?: boolean;
}

View File

@ -1,5 +1,6 @@
{
"$schema": "http://json-schema.org/schema",
"cli": "nx",
"id": "NxExpressInit",
"title": "Init Express Plugin",
"type": "object",

View File

@ -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'
);

View File

@ -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),
]);
}

View File

@ -1,4 +0,0 @@
export interface Schema {
unitTestRunner: 'jest' | 'none';
skipFormat: boolean;
}

View File

@ -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();
}

View File

@ -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';