diff --git a/e2e/node/src/__snapshots__/node-server.test.ts.snap b/e2e/node/src/__snapshots__/node-server.test.ts.snap new file mode 100644 index 0000000000000..908cdfd96b3e1 --- /dev/null +++ b/e2e/node/src/__snapshots__/node-server.test.ts.snap @@ -0,0 +1,29 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Node Applications + webpack should generate a Dockerfile 1`] = ` +"# This file is generated by Nx. +# +# Build the docker image with \`npx nx docker-build docker-express-app\`. +# Tip: Modify "docker-build" options in project.json to change docker build args. +# +# Run the container with \`docker run -p 3000:3000 -t docker-express-app\`. +FROM docker.io/node:lts-alpine + +ENV HOST=0.0.0.0 +ENV PORT=3000 + +WORKDIR /app + +RUN addgroup --system docker-express-app && \\ + adduser --system -G docker-express-app docker-express-app + +COPY dist/apps/docker-express-app docker-express-app/ +RUN chown -R docker-express-app:docker-express-app . + +# You can remove this install step if you build with \`--bundle\` option. +# The bundled output will include external dependencies. +RUN npm --prefix docker-express-app --omit=dev -f install + +CMD [ "node", "docker-express-app" ] +" +`; diff --git a/e2e/node/src/node-server.test.ts b/e2e/node/src/node-server.test.ts index b39f95520e899..a770db0d23f1f 100644 --- a/e2e/node/src/node-server.test.ts +++ b/e2e/node/src/node-server.test.ts @@ -2,6 +2,7 @@ import { checkFilesDoNotExist, checkFilesExist, cleanupProject, + getSelectedPackageManager, killPort, newProject, promisifiedTreeKill, @@ -14,11 +15,19 @@ import { } from '@nx/e2e/utils'; import { join } from 'path'; -xdescribe('Node Applications + webpack', () => { +describe('Node Applications + webpack', () => { let proj: string; + const selectedPm = getSelectedPackageManager(); + + // TODO(nicholas): Look into how this can work with npm instead of forcing pnpm. beforeAll(() => { proj = newProject({ packages: ['@nx/node'], + // npm has resolution for ajv some packages require ajv6 and some require ajv8 and npm resolves it to ajv6 (Error: Cannot find module 'ajv/dist/compile/codegen') + // - ajv@6 (fork-ts-checker-webpack-plugin, terser-webpack-plugin, webpack) + // - ajv@8 (babel-loader) + // Solution is to use pnpm or run fix it via npm ex.(npm dedupe --force) + packageManager: selectedPm === 'npm' ? 'pnpm' : selectedPm, }); }); @@ -47,10 +56,10 @@ xdescribe('Node Applications + webpack', () => { } } - async function runE2eTests(appName: string) { - process.env.PORT = '5000'; + async function runE2eTests(appName: string, port: number = 5000) { + process.env.PORT = `${port}`; const childProcess = await runCommandUntil(`serve ${appName}`, (output) => { - return output.includes('http://localhost:5000'); + return output.includes(`http://localhost:${port}`); }); const result = runCLI(`e2e ${appName}-e2e --verbose`); expect(result).toContain('Setting up...'); @@ -58,13 +67,11 @@ xdescribe('Node Applications + webpack', () => { expect(result).toContain('Successfully ran target e2e'); await promisifiedTreeKill(childProcess.pid, 'SIGKILL'); - await killPort(5000); + await killPort(port); process.env.PORT = ''; } - // Disabled due to flakiness of ajv disabled (Error: Cannot find module 'ajv/dist/compile/codegen') - // TODO: (nicholas) Re-enable when the flakiness is resolved - xit('should generate an app using webpack', async () => { + describe('frameworks', () => { const testLib1 = uniq('test1'); const testLib2 = uniq('test2'); const expressApp = uniq('expressapp'); @@ -72,79 +79,98 @@ xdescribe('Node Applications + webpack', () => { const koaApp = uniq('koaapp'); const nestApp = uniq('nest'); - runCLI(`generate @nx/node:lib ${testLib1}`); - runCLI(`generate @nx/node:lib ${testLib2} --importPath=@acme/test2`); - runCLI( - `generate @nx/node:app ${expressApp} --framework=express --no-interactive` - ); - runCLI( - `generate @nx/node:app ${fastifyApp} --framework=fastify --no-interactive` - ); - runCLI(`generate @nx/node:app ${koaApp} --framework=koa --no-interactive`); - runCLI( - `generate @nx/node:app ${nestApp} --framework=nest --bundler=webpack --no-interactive` - ); + beforeAll(() => { + runCLI(`generate @nx/node:lib ${testLib1}`); + runCLI(`generate @nx/node:lib ${testLib2} --importPath=@acme/test2`); + runCLI( + `generate @nx/node:app ${expressApp} --framework=express --port=7000 --no-interactive` + ); + runCLI( + `generate @nx/node:app ${fastifyApp} --framework=fastify --port=7001 --no-interactive` + ); + runCLI( + `generate @nx/node:app ${koaApp} --framework=koa --port=7002 --no-interactive` + ); + runCLI( + `generate @nx/node:app ${nestApp} --framework=nest --port=7003 --bundler=webpack --no-interactive` + ); - // Use esbuild by default - checkFilesDoNotExist(`apps/${expressApp}/webpack.config.js`); - checkFilesDoNotExist(`apps/${fastifyApp}/webpack.config.js`); - checkFilesDoNotExist(`apps/${koaApp}/webpack.config.js`); + addLibImport(expressApp, testLib1); + addLibImport(expressApp, testLib2, '@acme/test2'); + addLibImport(fastifyApp, testLib1); + addLibImport(fastifyApp, testLib2, '@acme/test2'); + addLibImport(koaApp, testLib1); + addLibImport(koaApp, testLib2, '@acme/test2'); - // Uses only webpack - checkFilesExist(`apps/${nestApp}/webpack.config.js`); + addLibImport(nestApp, testLib1); + addLibImport(nestApp, testLib2, '@acme/test2'); + }); - expect(() => runCLI(`lint ${expressApp}`)).not.toThrow(); - expect(() => runCLI(`lint ${fastifyApp}`)).not.toThrow(); - expect(() => runCLI(`lint ${koaApp}`)).not.toThrow(); - expect(() => runCLI(`lint ${nestApp}`)).not.toThrow(); + it('should generate an app defaults using webpack or esbuild', async () => { + // 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}-e2e`)).not.toThrow(); - expect(() => runCLI(`lint ${fastifyApp}-e2e`)).not.toThrow(); - expect(() => runCLI(`lint ${koaApp}-e2e`)).not.toThrow(); - expect(() => runCLI(`lint ${nestApp}-e2e`)).not.toThrow(); + // Uses only webpack + checkFilesExist(`apps/${nestApp}/webpack.config.js`); - // Only Fastify generates with unit tests since it supports them without additional libraries. - expect(() => runCLI(`test ${fastifyApp}`)).not.toThrow(); + expect(() => runCLI(`lint ${expressApp}`)).not.toThrow(); + expect(() => runCLI(`lint ${fastifyApp}`)).not.toThrow(); + expect(() => runCLI(`lint ${koaApp}`)).not.toThrow(); + expect(() => runCLI(`lint ${nestApp}`)).not.toThrow(); - // https://github.com/nrwl/nx/issues/16601 - const nestMainContent = readFile(`apps/${nestApp}/src/main.ts`); - updateFile( - `apps/${nestApp}/src/main.ts`, - ` + expect(() => runCLI(`lint ${expressApp}-e2e`)).not.toThrow(); + expect(() => runCLI(`lint ${fastifyApp}-e2e`)).not.toThrow(); + expect(() => runCLI(`lint ${koaApp}-e2e`)).not.toThrow(); + expect(() => runCLI(`lint ${nestApp}-e2e`)).not.toThrow(); + + // Only Fastify generates with unit tests since it supports them without additional libraries. + expect(() => runCLI(`test ${fastifyApp}`)).not.toThrow(); + + // https://github.com/nrwl/nx/issues/16601 + const nestMainContent = readFile(`apps/${nestApp}/src/main.ts`); + updateFile( + `apps/${nestApp}/src/main.ts`, + ` ${nestMainContent} // Make sure this is not replaced during build time console.log('env: ' + process.env['NODE_ENV']); ` - ); - runCLI(`build ${nestApp}`); - expect(readFile(`dist/apps/${nestApp}/main.js`)).toContain( - `'env: ' + process.env['NODE_ENV']` - ); + ); + runCLI(`build ${nestApp}`); + expect(readFile(`dist/apps/${nestApp}/main.js`)).toContain( + `'env: ' + process.env['NODE_ENV']` + ); + }, 300_000); - addLibImport(expressApp, testLib1); - addLibImport(expressApp, testLib2, '@acme/test2'); - addLibImport(fastifyApp, testLib1); - addLibImport(fastifyApp, testLib2, '@acme/test2'); - addLibImport(koaApp, testLib1); - addLibImport(koaApp, testLib2, '@acme/test2'); + it('should e2e test express app', async () => { + await runE2eTests(expressApp, 7000); + }); - addLibImport(nestApp, testLib1); - addLibImport(nestApp, testLib2, '@acme/test2'); + it('should e2e test fastify app', async () => { + await runE2eTests(fastifyApp, 7001); + }); - await runE2eTests(expressApp); - await runE2eTests(fastifyApp); - await runE2eTests(koaApp); - await runE2eTests(nestApp); - }, 900_000); + it('should e2e test koa app', async () => { + await runE2eTests(koaApp, 7002); + }); + + it('should e2e test nest app', async () => { + await runE2eTests(nestApp, 7003); + }); + }); it('should generate a Dockerfile', async () => { - const expressApp = uniq('expressapp'); + const expressApp = 'docker-express-app'; // needs to be consistent for the Dockerfile snapshot runCLI( - `generate @nx/node:app ${expressApp} --framework=express --docker --no-interactive` + `generate @nx/node:app ${expressApp} --framework=express --docker --no-interactive` ); checkFilesExist(`apps/${expressApp}/Dockerfile`); + const dockerFile = readFile(`apps/${expressApp}/Dockerfile`); + expect(dockerFile).toMatchSnapshot(); }, 300_000); it('should support waitUntilTargets for serve target', async () => { diff --git a/packages/node/src/generators/setup-docker/setup-docker.ts b/packages/node/src/generators/setup-docker/setup-docker.ts index 88461c9f31f2a..04ff39d8dfd27 100644 --- a/packages/node/src/generators/setup-docker/setup-docker.ts +++ b/packages/node/src/generators/setup-docker/setup-docker.ts @@ -27,12 +27,22 @@ function normalizeOptions( function addDocker(tree: Tree, options: SetUpDockerOptions) { const projectConfig = readProjectConfiguration(tree, options.project); + + const outputPath = + projectConfig.targets[options.buildTarget]?.options['outputPath']; + if (!projectConfig) { throw new Error(`Cannot find project configuration for ${options.project}`); } + + if (!outputPath && !options.outputPath) { + throw new Error( + `The output path for the project ${options.project} is not defined. Please provide it as an option to the generator.` + ); + } generateFiles(tree, join(__dirname, './files'), projectConfig.root, { tmpl: '', - buildLocation: options.outputPath, + buildLocation: options.outputPath ?? outputPath, project: options.project, }); }