import { checkFilesExist, cleanupProject, getSelectedPackageManager, getPackageManagerCommand, killPorts, newProject, promisifiedTreeKill, readJson, runCLI, runCommand, runCommandUntil, tmpProjPath, uniq, updateFile, updateJson, reservePort, } from '@nx/e2e-utils'; import { execSync } from 'child_process'; import * as http from 'http'; let originalEnvPort; describe('Node Applications', () => { const pm = getSelectedPackageManager(); let workspaceName: string; beforeAll(() => { originalEnvPort = process.env.PORT; workspaceName = newProject({ packages: [ '@nx/node', '@nx/express', '@nx/nest', '@nx/webpack', '@nx/jest', ], preset: 'ts', }); }); afterAll(() => { process.env.PORT = originalEnvPort; cleanupProject(); }); it('should be able to generate an empty application', async () => { const nodeapp = uniq('nodeapp'); const port = await reservePort(); process.env.PORT = `${port}`; runCLI( `generate @nx/node:app apps/${nodeapp} --port=${port} --linter=eslint --unitTestRunner=jest` ); expect(() => runCLI(`lint ${nodeapp}`)).not.toThrow(); expect(() => runCLI(`test ${nodeapp}`)).not.toThrow(); updateFile(`apps/${nodeapp}/src/main.ts`, `console.log('Hello World!');`); runCLI(`build ${nodeapp}`); checkFilesExist(`apps/${nodeapp}/dist/main.js`); const result = execSync(`node apps/${nodeapp}/dist/main.js`, { cwd: tmpProjPath(), }).toString(); expect(result).toContain('Hello World!'); await killPorts(port); }, 300_000); // TODO(jack): fix and re-enable xit('should be able to generate an express application', async () => { const nodeapp = uniq('nodeapp'); const nodelib = uniq('nodelib'); const port = await reservePort(); process.env.PORT = `${port}`; runCLI( `generate @nx/express:app apps/${nodeapp} --port=${port} --linter=eslint --unitTestRunner=jest` ); runCLI( `generate @nx/node:lib packages/${nodelib} --linter=eslint --unitTestRunner=jest` ); // No tests are generated by default, add a stub one. updateFile( `apps/${nodeapp}/src/app/test.spec.ts`, ` describe('test', () => { it('should work', () => { expect(true).toEqual(true); }) }) ` ); updateFile(`apps/${nodeapp}/src/assets/file.txt`, `Test`); updateFile(`apps/${nodeapp}/src/main.ts`, (content) => { return `import { ${nodelib} } from '@${workspaceName}/${nodelib}';\n${content}\nconsole.log(${nodelib}());`; }); // pnpm does not link packages unless they are deps // npm, yarn, and bun will link them in the root node_modules regardless if (pm === 'pnpm') { updateJson(`apps/${nodeapp}/package.json`, (json) => { json.dependencies = { [`@${workspaceName}/${nodelib}`]: 'workspace:*', }; return json; }); runCommand(`cd apps/${nodeapp} && ${getPackageManagerCommand().install}`); } runCLI(`sync`); expect(() => runCLI(`lint ${nodeapp}`)).not.toThrow(); expect(() => runCLI(`test ${nodeapp}`)).not.toThrow(); expect(() => runCLI(`build ${nodeapp}`)).not.toThrow(); expect(() => runCLI(`typecheck ${nodeapp}`)).not.toThrow(); expect(() => runCLI(`lint ${nodelib}`)).not.toThrow(); expect(() => runCLI(`test ${nodelib}`)).not.toThrow(); expect(() => runCLI(`build ${nodelib}`)).not.toThrow(); expect(() => runCLI(`typecheck ${nodelib}`)).not.toThrow(); const p = await runCommandUntil( `serve ${nodeapp}`, (output) => output.includes(`Listening at http://localhost:${port}`), { env: { NX_DAEMON: 'true', }, } ); let result = await getData(port); expect(result.message).toMatch(`Welcome to ${nodeapp}!`); result = await getData(port, '/assets/file.txt'); expect(result).toMatch(`Test`); try { await promisifiedTreeKill(p.pid, 'SIGKILL'); expect(await killPorts(port)).toBeTruthy(); } catch (err) { console.log( 'Error during cleanup (may be expected, especially ECONNRESET):', err.message ); } }, 300_000); it('should be able to generate a nest application', async () => { const nestapp = uniq('nodeapp'); const port = await reservePort(); process.env.PORT = `${port}`; runCLI( `generate @nx/nest:app apps/${nestapp} --linter=eslint --unitTestRunner=jest` ); expect(() => runCLI(`lint ${nestapp}`)).not.toThrow(); expect(() => runCLI(`test ${nestapp}`)).not.toThrow(); runCLI(`build ${nestapp}`); checkFilesExist(`apps/${nestapp}/dist/main.js`); const p = await runCommandUntil( `serve ${nestapp}`, (output) => output.includes( `Application is running on: http://localhost:${port}/api` ), { env: { NX_DAEMON: 'true', }, } ); const result = await getData(port, '/api'); expect(result.message).toMatch('Hello'); try { await promisifiedTreeKill(p.pid, 'SIGKILL'); expect(await killPorts(port)).toBeTruthy(); } catch (err) { console.log( 'Error during cleanup (may be expected, especially ECONNRESET):', err.message ); } }, 300_000); // TODO(jack): fix and re-enable xit('should be able to import a lib into a nest application', async () => { const nestApp = uniq('nestapp'); const nestLib = uniq('nestlib'); const port = await reservePort(); process.env.PORT = `${port}`; runCLI(`generate @nx/nest:app apps/${nestApp} --no-interactive`); runCLI(`generate @nx/nest:lib packages/${nestLib} --no-interactive`); updateFile(`apps/${nestApp}/src/app/app.module.ts`, (content) => { return `import '@${workspaceName}/${nestLib}';\n${content}\n`; }); if (pm === 'pnpm') { updateJson(`apps/${nestApp}/package.json`, (json) => { json.dependencies = { [`@${workspaceName}/${nestLib}`]: 'workspace:*', }; return json; }); runCommand(`${getPackageManagerCommand().install}`); } runCLI(`sync`); runCLI(`build ${nestApp} --verbose`); checkFilesExist(`apps/${nestApp}/dist/main.js`); const p = await runCommandUntil( `serve ${nestApp}`, (output) => output.includes( `Application is running on: http://localhost:${port}/api` ), { env: { NX_DAEMON: 'true', }, } ); const result = await getData(port, '/api'); expect(result.message).toMatch('Hello'); try { await promisifiedTreeKill(p.pid, 'SIGKILL'); expect(await killPorts(port)).toBeTruthy(); } catch (err) { console.log( 'Error during cleanup (may be expected, especially ECONNRESET):', err.message ); } }, 300_000); // Dependencies that are not directly imported but are still required for the build to succeed // App -> LibA -> LibB // LibB is transitive, it's not imported in the app, but is required by LibA it('should be able to work with transitive non-dependencies', async () => { const nestApp = uniq('nestApp'); const nestLibA = uniq('nestliba'); const nestLibB = uniq('nestlibb'); const port = await reservePort(); process.env.PORT = `${port}`; runCLI(`generate @nx/nest:app apps/${nestApp} --no-interactive`); runCLI(`generate @nx/nest:lib packages/${nestLibA} --no-interactive`); runCLI(`generate @nx/nest:lib packages/${nestLibB} --no-interactive`); if (pm === 'pnpm') { updateJson(`apps/${nestApp}/package.json`, (json) => { json.dependencies = { [`@${workspaceName}/${nestLibA}`]: 'workspace:*', }; return json; }); updateJson(`packages/${nestLibA}/package.json`, (json) => { json.dependencies = { [`@${workspaceName}/${nestLibB}`]: 'workspace:*', }; return json; }); runCommand(`${getPackageManagerCommand().install}`); } runCLI(`sync`); runCLI(`build ${nestApp} --verbose`); checkFilesExist(`apps/${nestApp}/dist/main.js`); const p = await runCommandUntil( `serve ${nestApp}`, (output) => output.includes( `Application is running on: http://localhost:${port}/api` ), { env: { NX_DAEMON: 'true', }, } ); const result = await getData(port, '/api'); expect(result.message).toMatch('Hello'); try { await promisifiedTreeKill(p.pid, 'SIGKILL'); expect(await killPorts(port)).toBeTruthy(); } catch (err) { console.log( 'Error during cleanup (may be expected, especially ECONNRESET):', err.message ); } }, 300_000); it('should respect and support generating libraries with a name different than the import path', () => { const nodeLib = uniq('node-lib'); const nestLib = uniq('nest-lib'); runCLI( `generate @nx/node:lib packages/${nodeLib} --name=${nodeLib} --buildable` ); runCLI( `generate @nx/nest:lib packages/${nestLib} --name=${nestLib} --buildable` ); const packageJson = readJson(`packages/${nodeLib}/package.json`); expect(packageJson.nx.name).toBe(nodeLib); const nestPackageJson = readJson(`packages/${nestLib}/package.json`); expect(nestPackageJson.nx.name).toBe(nestLib); expect(runCLI(`build ${nodeLib}`)).toContain( `Successfully ran target build for project ${nodeLib}` ); expect(runCLI(`build ${nestLib}`)).toContain( `Successfully ran target build for project ${nestLib}` ); }, 300_000); }); function getData(port, path = '/api'): Promise { return new Promise((resolve) => { http.get(`http://localhost:${port}${path}`, (res) => { expect(res.statusCode).toEqual(200); let data = ''; res.on('data', (chunk) => { data += chunk; }); res.once('end', () => { try { resolve(JSON.parse(data)); } catch (e) { resolve(data); } }); }); }); }