Files
nrwl--nx/e2e/node/src/node-ts-solution.test.ts
T
2026-07-13 12:38:36 +08:00

355 lines
10 KiB
TypeScript

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<any> {
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);
}
});
});
});
}