Skip to content

Commit

Permalink
feat(node): add e2e project generator
Browse files Browse the repository at this point in the history
  • Loading branch information
jaysoo authored and Jack Hsu committed Jan 17, 2023
1 parent e0bd338 commit a824611
Show file tree
Hide file tree
Showing 26 changed files with 444 additions and 18 deletions.
6 changes: 6 additions & 0 deletions docs/generated/packages/node/generators/application.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@
"default": false,
"hidden": true,
"x-priority": "internal"
},
"e2eTestRunner": {
"type": "string",
"enum": ["jest", "none"],
"description": "Test runner to use for end to end (e2e) tests",
"default": "jest"
}
},
"required": [],
Expand Down
64 changes: 64 additions & 0 deletions e2e/node/src/node-server.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import {
checkFilesDoNotExist,
cleanupProject,
killPort,
newProject,
promisifiedTreeKill,
runCLI,
runCommandUntil,
uniq,
} from '@nrwl/e2e/utils';
import { p } from 'vitest/dist/index-5aad25c1';

describe('Node Applications + webpack', () => {
beforeEach(() => newProject());

afterEach(() => cleanupProject());

async function runE2eTests(appName: string) {
process.env.PORT = '5000';
const childProcess = await runCommandUntil(`serve ${appName}`, (output) => {
return output.includes('http://localhost:5000');
});
const result = runCLI(`e2e ${appName}-e2e`);
expect(result).toContain('Setting up...');
expect(result).toContain('Tearing down..');
expect(result).toContain('Successfully ran target e2e');

await promisifiedTreeKill(childProcess.pid, 'SIGKILL');
await killPort(5000);
process.env.PORT = '';
}

it('should generate an app using webpack', async () => {
const expressApp = uniq('expressapp');
const fastifyApp = uniq('fastifyapp');
const koaApp = uniq('koaapp');

runCLI(
`generate @nrwl/node:app ${expressApp} --framework=express --no-interactive`
);
runCLI(
`generate @nrwl/node:app ${fastifyApp} --framework=fastify --no-interactive`
);
runCLI(
`generate @nrwl/node:app ${koaApp} --framework=koa --no-interactive`
);

// Use esbuild by default
checkFilesDoNotExist(`apps/${expressApp}/webpack.config.js`);
checkFilesDoNotExist(`apps/${fastifyApp}/webpack.config.js`);
checkFilesDoNotExist(`apps/${koaApp}/webpack.config.js`);

expect(() => runCLI(`lint ${expressApp}`)).not.toThrow();
expect(() => runCLI(`lint ${fastifyApp}`)).not.toThrow();
expect(() => runCLI(`lint ${koaApp}`)).not.toThrow();
expect(() => runCLI(`lint ${expressApp}-e2e`)).not.toThrow();
expect(() => runCLI(`lint ${fastifyApp}-e2e`)).not.toThrow();
expect(() => runCLI(`lint ${koaApp}-e2e`)).not.toThrow();

await runE2eTests(expressApp);
await runE2eTests(fastifyApp);
await runE2eTests(koaApp);
}, 300_000);
});
1 change: 0 additions & 1 deletion packages/node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
"@nrwl/linter": "file:../linter",
"@nrwl/webpack": "file:../webpack",
"@nrwl/workspace": "file:../workspace",
"enquirer": "~2.3.6",
"tslib": "^2.3.0"
},
"publishConfig": {
Expand Down
23 changes: 18 additions & 5 deletions packages/node/src/generators/application/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { Linter, lintProjectGenerator } from '@nrwl/linter';
import { jestProjectGenerator } from '@nrwl/jest';
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';

import { NodeJsFrameWorks, Schema } from './schema';
import { Schema } from './schema';
import { initGenerator } from '../init/init';
import { getRelativePathToRootTsConfig } from '@nrwl/workspace/src/utilities/typescript';
import {
Expand All @@ -41,9 +41,9 @@ import {
koaVersion,
nxVersion,
} from '../../utils/versions';
import { prompt } from 'enquirer';

import * as shared from '@nrwl/workspace/src/utils/create-ts-config';
import { e2eProjectGenerator } from '../e2e-project/e2e-project';

export interface NormalizedSchema extends Schema {
appProjectRoot: string;
Expand Down Expand Up @@ -317,15 +317,16 @@ function updateTsConfigOptions(tree: Tree, options: NormalizedSchema) {

export async function applicationGenerator(tree: Tree, schema: Schema) {
const options = normalizeOptions(tree, schema);

const tasks: GeneratorCallback[] = [];

const initTask = await initGenerator(tree, {
...options,
skipFormat: true,
});
tasks.push(initTask);

addProjectDependencies(tree, options);
const installTask = addProjectDependencies(tree, options);
tasks.push(installTask);
addAppFiles(tree, options);
addProject(tree, options);

Expand All @@ -346,12 +347,24 @@ export async function applicationGenerator(tree: Tree, schema: Schema) {
setupFile: 'none',
skipSerializers: true,
supportTsx: options.js,
babelJest: options.babelJest,
testEnvironment: 'node',
compiler: 'swc',
skipFormat: true,
});
tasks.push(jestTask);
}

if (options.e2eTestRunner === 'jest') {
const e2eTask = await e2eProjectGenerator(tree, {
...options,
projectType: options.framework === 'none' ? 'cli' : 'server',
name: `${options.name}-e2e`,
project: options.name,
port: options.port,
});
tasks.push(e2eTask);
}

if (options.js) {
updateTsConfigsToJs(tree, { projectRoot: options.appProjectRoot });
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import connect from 'connect';

const port = process.env.PORT ? Number(process.env.PORT) : <%= port %>;

const app = connect();

app.use((req, res) => {
res.end(JSON.stringify({ 'message': 'Hello API' }));
});

app.listen(<%= port %>, () => {
// Server is running
})
app.listen(port, () => {
console.log(`[ ready ] http://localhost:${port}`);
})
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import express from 'express';
const app = express();

const port = process.env.PORT ? Number(process.env.PORT) : <%= port %>;

const app = express();

app.get('/', (req, res) => {
res.send({ 'message': 'Hello API'});
});

app.listen(<%= port %>, () => {
// Server is running
});
app.listen(port, () => {
console.log(`[ ready ] http://localhost:${port}`);
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import fastify from 'fastify';

const port = process.env.PORT ? Number(process.env.PORT) : <%= port %>;

const app = fastify();

app.get('/', async (req, res) => {
Expand All @@ -8,11 +10,12 @@ app.get('/', async (req, res) => {

const start = async() => {
try {
await app.listen({ port: <%= port %> })
await app.listen({ port });
console.log(`[ ready ] http://localhost:${port}`);
} catch (err) {
// Errors are logged here
process.exit(1);
}
}

start();
start();
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import koa from 'koa';

const port = process.env.PORT ? Number(process.env.PORT) : <%= port %>;

const app = new koa();

app.use(async ctx =>{
ctx.body = { 'message': 'Hello API'};
});

app.listen(<%= port %>, () => {
// Server is running
});
app.listen(port, () => {
console.log(`[ ready ] http://localhost:${port}`);
});
1 change: 1 addition & 0 deletions packages/node/src/generators/application/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export interface Schema {
skipPackageJson?: boolean;
directory?: string;
unitTestRunner?: 'jest' | 'none';
e2eTestRunner?: 'jest' | 'none';
linter?: Linter;
tags?: string;
frontendProject?: string;
Expand Down
6 changes: 6 additions & 0 deletions packages/node/src/generators/application/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@
"default": false,
"hidden": true,
"x-priority": "internal"
},
"e2eTestRunner": {
"type": "string",
"enum": ["jest", "none"],
"description": "Test runner to use for end to end (e2e) tests",
"default": "jest"
}
},
"required": []
Expand Down
121 changes: 121 additions & 0 deletions packages/node/src/generators/e2e-project/e2e-project.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import * as path from 'path';
import {
addDependenciesToPackageJson,
addProjectConfiguration,
convertNxGenerator,
extractLayoutDirectory,
formatFiles,
generateFiles,
GeneratorCallback,
getWorkspaceLayout,
joinPathFragments,
names,
offsetFromRoot,
readProjectConfiguration,
Tree,
} from '@nrwl/devkit';
import { Linter, lintProjectGenerator } from '@nrwl/linter';

import { Schema } from './schema';
import { axiosVersion } from '../../utils/versions';
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';

export async function e2eProjectGenerator(host: Tree, _options: Schema) {
const tasks: GeneratorCallback[] = [];
const options = normalizeOptions(host, _options);
const appProject = readProjectConfiguration(host, options.project);

addProjectConfiguration(host, options.projectName, {
root: options.projectRoot,
targets: {
e2e: {
executor: '@nrwl/jest:jest',
outputs: ['{workspaceRoot}/coverage/{projectRoot}'],
options: {
jestConfig: `${options.projectRoot}/jest.config.ts`,
passWithNoTests: true,
},
},
},
});

if (options.projectType === 'server') {
generateFiles(
host,
path.join(__dirname, 'files/server'),
options.projectRoot,
{
...options,
...names(options.projectName),
offsetFromRoot: offsetFromRoot(options.projectRoot),
tmpl: '',
}
);
} else if (options.projectType === 'cli') {
const mainFile = appProject.targets.build?.options?.outputPath;
generateFiles(
host,
path.join(__dirname, 'files/cli'),
options.projectRoot,
{
...options,
...names(options.projectName),
mainFile,
offsetFromRoot: offsetFromRoot(options.projectRoot),
tmpl: '',
}
);
}

// axios is more than likely used in the application code, so install it as a regular dependency.
const installTask = addDependenciesToPackageJson(
host,
{ axios: axiosVersion },
{}
);
tasks.push(installTask);

if (options.linter === 'eslint') {
const linterTask = await lintProjectGenerator(host, {
project: options.projectName,
linter: Linter.EsLint,
skipFormat: true,
tsConfigPaths: [joinPathFragments(options.projectRoot, 'tsconfig.json')],
eslintFilePatterns: [`${options.projectRoot}/**/*.{js,ts}`],
setParserOptionsProject: false,
skipPackageJson: false,
rootProject: options.rootProject,
});
tasks.push(linterTask);
}

await formatFiles(host);

return runTasksInSerial(...tasks);
}

function normalizeOptions(
tree: Tree,
options: Schema
): Omit<Schema, 'name'> & { projectRoot: string; projectName: string } {
const { layoutDirectory, projectDirectory } = extractLayoutDirectory(
options.directory
);
const appsDir = layoutDirectory ?? getWorkspaceLayout(tree).appsDir;
const name = options.name ?? `${options.project}-e2e`;

const appDirectory = projectDirectory
? `${names(projectDirectory).fileName}/${names(name).fileName}`
: names(name).fileName;

const projectName = appDirectory.replace(new RegExp('/', 'g'), '-');

const projectRoot = options.rootProject
? '.'
: joinPathFragments(appsDir, appDirectory);

return { ...options, projectRoot, projectName, port: options.port ?? 3000 };
}

export default e2eProjectGenerator;
export const e2eProjectSchematic = convertNxGenerator(e2eProjectGenerator);
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/* eslint-disable */
export default {
displayName: '<%= projectName %>',
preset: '<%= offsetFromRoot %>/jest.preset.js',
globals: {
'ts-jest': {
tsconfig: '<rootDir>/tsconfig.spec.json',
},
},
setupFiles: ['<rootDir>/src/test-setup.ts'],
testEnvironment: 'node',
transform: {
'^.+\\.[tj]s$': 'ts-jest',
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '<%= offsetFromRoot %>/coverage/<%= projectName %>',
};
Loading

0 comments on commit a824611

Please sign in to comment.