chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:38:36 +08:00
commit 8e2a6eb840
10194 changed files with 1593658 additions and 0 deletions
+104
View File
@@ -0,0 +1,104 @@
import { names } from '@nx/devkit';
import {
cleanupProject,
getPackageManagerCommand,
newProject,
reservePort,
runCLI,
runCommand,
isDockerAvailable,
uniq,
updateFile,
updateJson,
} from '@nx/e2e-utils';
let originalEnvPort;
describe('Node Docker Applications', () => {
if (isDockerAvailable()) {
describe.each(['npm', 'yarn', 'pnpm'])(
'%s',
(packageManager: 'npm' | 'yarn' | 'pnpm') => {
beforeEach(() => {
originalEnvPort = process.env.PORT;
newProject({
preset: 'ts',
packageManager,
});
});
afterEach(() => {
process.env.PORT = originalEnvPort;
cleanupProject();
});
it(`should generate ${packageManager} compliant dockerfile and use it`, async () => {
const { nodeapp } = await setupTest(packageManager);
expect(runCLI(`docker:build ${nodeapp} --network=host`)).toContain(
`Successfully ran target docker:build for project @proj/${nodeapp}`
);
});
}
);
} else {
it('docker is not available', () => expect(true).toBe(true));
}
});
async function setupTest(packageManager: 'npm' | 'yarn' | 'pnpm' | 'bun') {
const nodeapp = uniq('nodeapp');
const lib = uniq('lib');
const port = await reservePort();
process.env.PORT = `${port}`;
runCLI(
`generate @nx/node:app apps/${nodeapp} --port=${port} --bundler=esbuild --framework=fastify --docker=true --no-interactive`
);
runCLI(
`generate @nx/js:lib packages/${lib} --bundler=none --e2eTestRunner=none --unitTestRunner=none`
);
updateFile(
`apps/${nodeapp}/src/main.ts`,
(content) => `import { ${names(lib).propertyName} } from '@proj/${lib}';
console.log(${names(lib).propertyName}());
${content}
`
);
// App is CJS by default so lets update the lib to follow the same pattern
updateJson(`packages/${lib}/tsconfig.lib.json`, (json) => {
json.compilerOptions.module = 'commonjs';
json.compilerOptions.moduleResolution = 'node';
return json;
});
updateJson('tsconfig.base.json', (json) => {
json.compilerOptions.moduleResolution = 'node';
json.compilerOptions.module = 'esnext';
delete json.compilerOptions.customConditions;
return json;
});
if (packageManager === 'pnpm' || packageManager === 'yarn') {
updateJson(`apps/${nodeapp}/package.json`, (json) => {
json.dependencies ??= {};
json.dependencies[`@proj/${lib}`] = 'workspace:*';
return json;
});
} else if (packageManager === 'npm') {
updateJson(`apps/${nodeapp}/package.json`, (json) => {
json.dependencies ??= {};
json.dependencies[`@proj/${lib}`] = '*';
return json;
});
}
const pmc = getPackageManagerCommand({ packageManager });
runCommand(pmc.install);
runCLI('sync');
return { nodeapp };
}
+50
View File
@@ -0,0 +1,50 @@
import {
checkFilesDoNotExist,
checkFilesExist,
cleanupProject,
newProject,
promisifiedTreeKill,
readFile,
runCLI,
runCommandUntil,
uniq,
updateFile,
} from '@nx/e2e-utils';
describe('Node Applications + esbuild', () => {
beforeAll(() =>
newProject({
packages: ['@nx/node'],
})
);
afterAll(() => cleanupProject());
it('should generate an app using esbuild', async () => {
const app = uniq('nodeapp');
runCLI(
`generate @nx/node:app apps/${app} --bundler=esbuild --no-interactive --linter=eslint --unitTestRunner=jest`
);
checkFilesDoNotExist(`apps/${app}/webpack.config.js`);
updateFile(`apps/${app}/src/main.ts`, `console.log('Hello World!');`);
const p = await runCommandUntil(`serve ${app} --watch=false`, (output) => {
process.stdout.write(output);
return output.includes(`Hello World!`);
});
checkFilesExist(`dist/apps/${app}/main.js`);
// Check that updating the file won't trigger a rebuild since --watch=false.
updateFile(`apps/${app}/src/main.ts`, `console.log('Bye1');`);
await new Promise((res) => setTimeout(res, 2000));
expect(readFile(`dist/apps/${app}/apps/${app}/src/main.js`)).not.toContain(
`Bye!`
);
await promisifiedTreeKill(p.pid, 'SIGKILL');
}, 300_000);
});
+577
View File
@@ -0,0 +1,577 @@
import { stripIndents } from '@angular-devkit/core/src/utils/literals';
import {
checkFilesExist,
cleanupProject,
getPackageManagerCommand,
newProject,
reservePort,
runCLI,
runCLIAsync,
runCommand,
runCommandUntil,
promisifiedTreeKill,
tmpProjPath,
uniq,
updateFile,
updateJson,
detectPackageManager,
} from '@nx/e2e-utils';
import { execSync } from 'child_process';
// Starts a built node server app, polls its output until it reports it is
// listening (up to 30s), then kills it and returns everything it printed.
// Polling for the "ready" line avoids a fixed `sleep` race that loses on a
// loaded machine, where node boot + module evaluation can take several seconds.
function runBuiltApp(appName: string): string {
return execSync(
`node dist/apps/${appName}/main.js > ${appName}-run.log 2>&1 & PID=$!; ` +
`for i in $(seq 1 30); do grep -q "server ready on port" ${appName}-run.log 2>/dev/null && break; sleep 1; done; ` +
`kill $PID 2>/dev/null || true; wait $PID 2>/dev/null || true; cat ${appName}-run.log`,
{ cwd: tmpProjPath(), timeout: 60_000 }
).toString();
}
function configureAsEsm(projectPath: string) {
// Update project.json to configure build target for ESM
const projectJsonPath = `${projectPath}/project.json`;
updateJson(projectJsonPath, (config) => {
if (config.targets?.build?.options) {
// Configure build executor to output ESM format
config.targets.build.options.format = ['esm'];
}
return config;
});
// Also update TypeScript config to match ESM
const tsconfigPath = `${projectPath}/tsconfig.app.json`;
updateJson(tsconfigPath, (config) => {
if (config.compilerOptions) {
config.compilerOptions.module = 'ES2022';
config.compilerOptions.target = 'ES2022';
}
return config;
});
}
const selectedPm = detectPackageManager();
describe('Node.js Framework ESM Support', () => {
beforeAll(() => {
newProject({
packages: ['@nx/node', '@nx/nest', '@nx/webpack'],
packageManager: selectedPm === 'npm' ? 'pnpm' : selectedPm,
});
});
afterAll(() => {
cleanupProject();
});
describe('Express Framework with ESM', () => {
it('should build and run Express app with ESM modules', async () => {
const port = await reservePort();
const expressApp = uniq('express-esm');
runCLI(
`generate @nx/node:app apps/${expressApp} --framework=express --linter=eslint --unitTestRunner=jest`
);
// Install node-fetch (ESM-only package)
const pmc = getPackageManagerCommand();
runCommand(`${pmc.addDev} node-fetch@3.3.0`);
// Configure as ESM
configureAsEsm(`apps/${expressApp}`);
// Update the Express app to use ESM imports
updateFile(
`apps/${expressApp}/src/main.ts`,
stripIndents`
import express from 'express';
import fetch from 'node-fetch';
const app = express();
app.get('/api', (req, res) => {
res.json({
message: 'Welcome to ${expressApp}!',
framework: 'express',
fetch_available: typeof fetch === 'function'
});
});
console.log('Express ESM app starting');
console.log('fetch type:', typeof fetch);
const port = process.env.PORT || ${port};
const server = app.listen(port, () => {
console.log('Express ESM server ready on port ' + port);
});
server.on('error', console.error);
`
);
await runCLIAsync(`build ${expressApp}`);
checkFilesExist(`dist/apps/${expressApp}/main.js`);
const result = runBuiltApp(expressApp);
expect(result).toContain('Express ESM app starting');
expect(result).toContain('Express ESM server ready on port');
expect(result).toContain('fetch type: function');
}, 600000);
it('should serve Express app with ESM modules', async () => {
const port = await reservePort();
const expressApp = uniq('express-esm-serve');
runCLI(
`generate @nx/node:app apps/${expressApp} --framework=express --linter=eslint --unitTestRunner=jest`
);
// Install node-fetch (ESM-only package)
const pmc = getPackageManagerCommand();
runCommand(`${pmc.addDev} node-fetch@3.3.0`);
// Configure as ESM
configureAsEsm(`apps/${expressApp}`);
// Create Express server with ESM imports
updateFile(
`apps/${expressApp}/src/main.ts`,
stripIndents`
import express from 'express';
import fetch from 'node-fetch';
const app = express();
console.log('Express ESM serve starting');
console.log('fetch type:', typeof fetch);
app.get('/api', (req, res) => {
res.json({ message: 'Express ESM serve working' });
});
const port = process.env.PORT || ${port};
const server = app.listen(port, () => {
console.log('Express ESM serve ready on port ' + port);
});
server.on('error', console.error);
`
);
const serveProcess = await runCommandUntil(
`serve ${expressApp}`,
(output) => {
return output.includes('Express ESM serve ready on port');
},
{ timeout: 120000 }
);
await promisifiedTreeKill(serveProcess.pid, 'SIGKILL');
}, 600000);
});
describe('Fastify Framework with ESM', () => {
it('should build and run Fastify app with ESM modules', async () => {
const port = await reservePort();
const fastifyApp = uniq('fastify-esm');
runCLI(
`generate @nx/node:app apps/${fastifyApp} --framework=fastify --linter=eslint --unitTestRunner=jest`
);
// Install node-fetch (ESM-only package)
const pmc = getPackageManagerCommand();
runCommand(`${pmc.addDev} node-fetch@3.3.0`);
// Configure as ESM
configureAsEsm(`apps/${fastifyApp}`);
// Update the Fastify app to use ESM imports
updateFile(
`apps/${fastifyApp}/src/main.ts`,
stripIndents`
import Fastify from 'fastify';
import fetch from 'node-fetch';
const fastify = Fastify({
logger: false
});
fastify.get('/api', async (request, reply) => {
return {
message: 'Welcome to ${fastifyApp}!',
framework: 'fastify',
fetch_available: typeof fetch === 'function'
};
});
console.log('Fastify ESM app starting');
console.log('fetch type:', typeof fetch);
const start = async () => {
try {
const port = +(process.env.PORT || ${port});
await fastify.listen({ port });
console.log('Fastify ESM server ready on port ' + port);
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
};
start();
`
);
await runCLIAsync(`build ${fastifyApp}`);
checkFilesExist(`dist/apps/${fastifyApp}/main.js`);
const result = runBuiltApp(fastifyApp);
expect(result).toContain('Fastify ESM app starting');
expect(result).toContain('Fastify ESM server ready on port');
expect(result).toContain('fetch type: function');
}, 600000);
it('should serve Fastify app with ESM modules', async () => {
const port = await reservePort();
const fastifyApp = uniq('fastify-esm-serve');
runCLI(
`generate @nx/node:app apps/${fastifyApp} --framework=fastify --linter=eslint --unitTestRunner=jest`
);
// Install node-fetch (ESM-only package)
const pmc = getPackageManagerCommand();
runCommand(`${pmc.addDev} node-fetch@3.3.0`);
// Configure as ESM
configureAsEsm(`apps/${fastifyApp}`);
// Create Fastify server with ESM imports
updateFile(
`apps/${fastifyApp}/src/main.ts`,
stripIndents`
import Fastify from 'fastify';
import fetch from 'node-fetch';
const fastify = Fastify({ logger: false });
console.log('Fastify ESM serve starting');
console.log('fetch type:', typeof fetch);
fastify.get('/api', async (request, reply) => {
return { message: 'Fastify ESM serve working' };
});
const start = async () => {
try {
const port = +(process.env.PORT || ${port});
await fastify.listen({ port });
console.log('Fastify ESM serve ready on port ' + port);
} catch (err) {
process.exit(1);
}
};
start();
`
);
const serveProcess = await runCommandUntil(
`serve ${fastifyApp}`,
(output) => {
return output.includes('Fastify ESM serve ready on port');
},
{ timeout: 120000 }
);
await promisifiedTreeKill(serveProcess.pid, 'SIGKILL');
}, 600000);
});
describe('Koa Framework with ESM', () => {
it('should build and run Koa app with ESM modules', async () => {
const port = await reservePort();
const koaApp = uniq('koa-esm');
runCLI(
`generate @nx/node:app apps/${koaApp} --framework=koa --linter=eslint --unitTestRunner=jest`
);
// Install node-fetch (ESM-only package) and koa router
const pmc = getPackageManagerCommand();
runCommand(
`${pmc.addDev} node-fetch@3.3.0 @koa/router @types/koa__router`
);
// Configure as ESM
configureAsEsm(`apps/${koaApp}`);
// Update the Koa app to use ESM imports
updateFile(
`apps/${koaApp}/src/main.ts`,
stripIndents`
import Koa from 'koa';
import Router from '@koa/router';
import fetch from 'node-fetch';
const app = new Koa();
const router = new Router();
router.get('/api', (ctx) => {
ctx.body = {
message: 'Welcome to ${koaApp}!',
framework: 'koa',
fetch_available: typeof fetch === 'function'
};
});
app.use(router.routes()).use(router.allowedMethods());
console.log('Koa ESM app starting');
console.log('fetch type:', typeof fetch);
const port = +(process.env.PORT || ${port});
const server = app.listen(port, () => {
console.log('Koa ESM server ready on port ' + port);
});
server.on('error', console.error);
`
);
await runCLIAsync(`build ${koaApp}`);
checkFilesExist(`dist/apps/${koaApp}/main.js`);
const result = runBuiltApp(koaApp);
expect(result).toContain('Koa ESM app starting');
expect(result).toContain('Koa ESM server ready on port');
expect(result).toContain('fetch type: function');
}, 600000);
it('should serve Koa app with ESM modules', async () => {
const port = await reservePort();
const koaApp = uniq('koa-esm-serve');
runCLI(
`generate @nx/node:app apps/${koaApp} --framework=koa --linter=eslint --unitTestRunner=jest`
);
// Install node-fetch (ESM-only package) and koa router
const pmc = getPackageManagerCommand();
runCommand(
`${pmc.addDev} node-fetch@3.3.0 @koa/router @types/koa__router`
);
// Configure as ESM
configureAsEsm(`apps/${koaApp}`);
// Create Koa server with ESM imports
updateFile(
`apps/${koaApp}/src/main.ts`,
stripIndents`
import Koa from 'koa';
import Router from '@koa/router';
import fetch from 'node-fetch';
const app = new Koa();
const router = new Router();
console.log('Koa ESM serve starting');
console.log('fetch type:', typeof fetch);
router.get('/api', (ctx) => {
ctx.body = { message: 'Koa ESM serve working' };
});
app.use(router.routes()).use(router.allowedMethods());
const port = +(process.env.PORT || ${port});
const server = app.listen(port, () => {
console.log('Koa ESM serve ready on port ' + port);
});
server.on('error', console.error);
`
);
const serveProcess = await runCommandUntil(
`serve ${koaApp}`,
(output) => {
return output.includes('Koa ESM serve ready on port');
},
{ timeout: 120000 }
);
await promisifiedTreeKill(serveProcess.pid, 'SIGKILL');
}, 600000);
});
describe('Nest Framework with ESM', () => {
it('should build and run Nest app with ESM modules', async () => {
const port = await reservePort();
const nestApp = uniq('nest-esm');
runCLI(
`generate @nx/node:app apps/${nestApp} --framework=nest --linter=eslint --unitTestRunner=jest`
);
// Install node-fetch (ESM-only package)
const pmc = getPackageManagerCommand();
runCommand(`${pmc.addDev} node-fetch@3.3.0`);
// Configure as ESM
configureAsEsm(`apps/${nestApp}`);
// Update the Nest app to use ESM imports
updateFile(
`apps/${nestApp}/src/main.ts`,
stripIndents`
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app/app.module';
async function bootstrap() {
console.log('Nest ESM app starting');
try {
// Test ESM-only package import
const { default: fetch } = await import('node-fetch');
console.log('fetch type:', typeof fetch);
} catch (error) {
console.error('Failed to import node-fetch:', error.message);
}
const app = await NestFactory.create(AppModule);
const globalPrefix = 'api';
app.setGlobalPrefix(globalPrefix);
const port = process.env.PORT || ${port};
await app.listen(port);
console.log('Nest ESM server ready on port ' + port);
}
bootstrap();
`
);
await runCLIAsync(`build ${nestApp}`);
checkFilesExist(`dist/apps/${nestApp}/main.js`);
const serveProcess = await runCommandUntil(
`serve ${nestApp}`,
(output) => {
return output.includes('Nest ESM server ready on port');
},
{ timeout: 120000 }
);
await promisifiedTreeKill(serveProcess.pid, 'SIGKILL');
}, 600000);
it('should serve Nest app with ESM modules', async () => {
const port = await reservePort();
const nestApp = uniq('nest-esm-serve');
runCLI(
`generate @nx/node:app apps/${nestApp} --framework=nest --linter=eslint --unitTestRunner=jest`
);
// Install node-fetch (ESM-only package) and ajv@8 to fix dependency conflicts
const pmc = getPackageManagerCommand();
runCommand(`${pmc.addDev} node-fetch@3.3.0`);
runCommand(`${pmc.addDev} ajv@^8.17.1`);
// Configure as ESM
configureAsEsm(`apps/${nestApp}`);
// Create Nest server with ESM imports
updateFile(
`apps/${nestApp}/src/main.ts`,
stripIndents`
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app/app.module';
async function bootstrap() {
console.log('Nest ESM serve starting');
try {
const { default: fetch } = await import('node-fetch');
console.log('fetch type:', typeof fetch);
} catch (error) {
console.error('Failed to import node-fetch:', error.message);
}
const app = await NestFactory.create(AppModule);
const globalPrefix = 'api';
app.setGlobalPrefix(globalPrefix);
const port = process.env.PORT || ${port};
await app.listen(port);
console.log('Nest ESM serve ready on port ' + port);
}
bootstrap();
`
);
const serveProcess = await runCommandUntil(
`serve ${nestApp}`,
(output) => {
return output.includes('Nest ESM serve ready on port');
},
{ timeout: 120000 }
);
await promisifiedTreeKill(serveProcess.pid, 'SIGKILL');
}, 600000);
});
describe('Mixed Imports across Node.js Frameworks', () => {
it('should handle CommonJS and ESM packages together', async () => {
const port = await reservePort();
const nodeApp = uniq('node-mixed-imports');
runCLI(
`generate @nx/node:app apps/${nodeApp} --framework=express --linter=eslint --unitTestRunner=jest`
);
// Install packages with different module formats
const pmc = getPackageManagerCommand();
runCommand(
`${pmc.addDev} express @types/express node-fetch@3.3.0 lodash @types/lodash`
);
// Create an Express app that uses both CommonJS (lodash) and ESM (node-fetch) packages
updateFile(
`apps/${nodeApp}/src/main.ts`,
stripIndents`
import express from 'express';
import * as lodash from 'lodash';
const app = express();
app.get('/api', async (req, res) => {
try {
// Import ESM-only package dynamically
const { default: fetch } = await import('node-fetch');
// Use CommonJS package (lodash)
const data = lodash.pick({ a: 1, b: 2, c: 3 }, ['a', 'b']);
res.json({
message: 'Welcome to ${nodeApp}!',
data,
fetch_available: typeof fetch === 'function',
lodash_available: typeof lodash.pick === 'function'
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
console.log('Mixed imports Node.js app starting');
console.log('lodash.pick type:', typeof lodash.pick);
const port = process.env.PORT || ${port};
const server = app.listen(port, () => {
console.log('Mixed imports server ready on port ' + port);
});
server.on('error', console.error);
`
);
await runCLIAsync(`build ${nodeApp}`);
checkFilesExist(`dist/apps/${nodeApp}/main.js`);
const result = runBuiltApp(nodeApp);
expect(result).toContain('Mixed imports Node.js app starting');
expect(result).toContain('Mixed imports server ready on port');
expect(result).toContain('lodash.pick type: function');
}, 600000);
});
});
+268
View File
@@ -0,0 +1,268 @@
import {
checkFilesDoNotExist,
checkFilesExist,
cleanupProject,
getSelectedPackageManager,
killPort,
killProcessAndPorts,
newProject,
readFile,
reservePort,
runCLI,
runCommandUntil,
uniq,
updateFile,
updateJson,
} from '@nx/e2e-utils';
import { join } from 'path';
import { stripVTControlCharacters } from 'node:util';
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', '@nx/nest', '@nx/webpack'],
// 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,
});
});
afterAll(() => cleanupProject());
function addLibImport(appName: string, libName: string, importPath?: string) {
const content = readFile(`apps/${appName}/src/main.ts`);
if (importPath) {
updateFile(
`apps/${appName}/src/main.ts`,
`
import { ${libName} } from '${importPath}';
${content}
console.log(${libName}());
`
);
} else {
updateFile(
`apps/${appName}/src/main.ts`,
`
import { ${libName} } from '@${proj}/${libName}';
${content}
console.log(${libName}());
`
);
}
}
async function runE2eTests(appName: string, port: number) {
process.env.PORT = `${port}`;
const result = runCLI(`e2e ${appName}-e2e --verbose`);
expect(result).toContain('Setting up...');
expect(result).toContain('Tearing down..');
expect(result).toContain('Successfully ran target e2e');
await killPort(port);
process.env.PORT = '';
}
describe('frameworks', () => {
const testLib1 = uniq('test1');
const testLib2 = uniq('test2');
const expressApp = uniq('expressapp');
const fastifyApp = uniq('fastifyapp');
const koaApp = uniq('koaapp');
const nestApp = uniq('nest');
let expressPort: number;
let fastifyPort: number;
let koaPort: number;
let nestPort: number;
beforeAll(async () => {
expressPort = await reservePort();
fastifyPort = await reservePort();
koaPort = await reservePort();
nestPort = await reservePort();
runCLI(
`generate @nx/node:lib libs/${testLib1} --linter=eslint --unitTestRunner=jest --buildable=false`
);
runCLI(
`generate @nx/node:lib libs/${testLib2} --importPath=@acme/test2 --linter=eslint --unitTestRunner=jest --buildable=false`
);
runCLI(
`generate @nx/node:app apps/${expressApp} --framework=express --port=${expressPort} --no-interactive --linter=eslint --unitTestRunner=jest --e2eTestRunner=jest`
);
runCLI(
`generate @nx/node:app apps/${fastifyApp} --framework=fastify --port=${fastifyPort} --no-interactive --linter=eslint --unitTestRunner=jest --e2eTestRunner=jest`
);
runCLI(
`generate @nx/node:app apps/${koaApp} --framework=koa --port=${koaPort} --no-interactive --linter=eslint --unitTestRunner=jest --e2eTestRunner=jest`
);
runCLI(
`generate @nx/node:app apps/${nestApp} --framework=nest --port=${nestPort} --bundler=webpack --no-interactive --linter=eslint --unitTestRunner=jest --e2eTestRunner=jest --verbose`
);
addLibImport(expressApp, testLib1);
addLibImport(expressApp, testLib2, '@acme/test2');
addLibImport(fastifyApp, testLib1);
addLibImport(fastifyApp, testLib2, '@acme/test2');
addLibImport(koaApp, testLib1);
addLibImport(koaApp, testLib2, '@acme/test2');
addLibImport(nestApp, testLib1);
addLibImport(nestApp, testLib2, '@acme/test2');
});
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`);
// Uses only webpack
checkFilesExist(`apps/${nestApp}/webpack.config.js`);
expect(() => runCLI(`lint ${expressApp}`)).not.toThrow();
expect(() => runCLI(`lint ${fastifyApp}`)).not.toThrow();
expect(() => runCLI(`lint ${koaApp}`)).not.toThrow();
expect(() => runCLI(`lint ${nestApp}`)).not.toThrow();
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']`
);
}, 300_000);
it('should e2e test express app', async () => {
await runE2eTests(expressApp, expressPort);
});
it('should e2e test fastify app', async () => {
await runE2eTests(fastifyApp, fastifyPort);
});
it('should e2e test koa app', async () => {
await runE2eTests(koaApp, koaPort);
});
it('should e2e test nest app', async () => {
await runE2eTests(nestApp, nestPort);
});
});
it('should generate a Dockerfile', async () => {
const expressApp = 'docker-express-app'; // needs to be consistent for the Dockerfile snapshot
runCLI(
`generate @nx/node:app apps/${expressApp} --framework=express --docker --no-interactive --linter=eslint --unitTestRunner=jest`
);
checkFilesExist(`apps/${expressApp}/Dockerfile`);
}, 300_000);
it('should support waitUntilTargets for serve target', async () => {
const nodeApp1 = uniq('nodeapp1');
const nodeApp2 = uniq('nodeapp2');
const nodeApp1Port = await reservePort();
const nodeApp2Port = await reservePort();
// Set ports to avoid conflicts with other tests that might run in parallel
runCLI(
`generate @nx/node:app apps/${nodeApp1} --framework=none --no-interactive --port=${nodeApp1Port} --linter=eslint --unitTestRunner=jest`
);
runCLI(
`generate @nx/node:app apps/${nodeApp2} --framework=none --no-interactive --port=${nodeApp2Port} --linter=eslint --unitTestRunner=jest`
);
updateJson(join('apps', nodeApp1, 'project.json'), (config) => {
config.targets.serve.options.waitUntilTargets = [`${nodeApp2}:build`];
return config;
});
await runCommandUntil(`serve ${nodeApp1} `, (output) =>
output.includes('Hello World')
);
checkFilesExist(`dist/apps/${nodeApp1}/main.js`);
checkFilesExist(`dist/apps/${nodeApp2}/main.js`);
}, 300_000);
it('should wait for the previous process to exit on watch restart so the port is released', async () => {
const app = uniq('slowshutdown');
const port = await reservePort();
runCLI(
`generate @nx/node:app apps/${app} --framework=none --no-interactive --port=${port} --linter=eslint --unitTestRunner=jest`
);
// Server with a slow graceful shutdown that holds the port after SIGTERM.
// A watch-mode restart must wait for it to exit, otherwise the new
// process fails with EADDRINUSE. The 8s hold exceeds the 5s kill grace
// period, so the executor force-kills it rather than waiting forever.
updateFile(
`apps/${app}/src/main.ts`,
`
import { createServer } from 'http';
const server = createServer((req, res) => res.end('ok'));
server.listen(${port}, () => console.log('Listening on ${port} pid ' + process.pid));
process.on('SIGTERM', () => {
setTimeout(() => process.exit(0), 8000);
});
`
);
const p = await runCommandUntil(`serve ${app}`, (output) =>
output.includes(`Listening on ${port}`)
);
try {
const restarted = new Promise<void>((resolve, reject) => {
let output = '';
const timeoutId = setTimeout(() => {
reject(new Error(`Server did not restart. Output:\n${output}`));
}, 120_000);
const check = (chunk: Buffer) => {
output += stripVTControlCharacters(chunk.toString());
if (output.includes('EADDRINUSE')) {
clearTimeout(timeoutId);
reject(new Error(`Restarted server hit EADDRINUSE:\n${output}`));
} else if (output.includes(`Listening on ${port}`)) {
clearTimeout(timeoutId);
resolve();
}
};
p.stdout?.on('data', check);
p.stderr?.on('data', check);
});
// Trigger a watch rebuild and restart.
updateFile(`apps/${app}/src/main.ts`, (content) =>
content.replace(`res.end('ok')`, `res.end('ok v2')`)
);
await restarted;
} finally {
await killProcessAndPorts(p.pid, port);
}
}, 300_000);
});
@@ -0,0 +1,88 @@
import { names } from '@nx/devkit';
import {
cleanupProject,
getPackageManagerCommand,
getSelectedPackageManager,
newProject,
reservePort,
runCLI,
runCommand,
uniq,
updateFile,
updateJson,
} from '@nx/e2e-utils';
let originalEnvPort;
describe('Node Esbuild Applications', () => {
beforeAll(() => {
originalEnvPort = process.env.PORT;
newProject({
preset: 'ts',
});
});
afterAll(() => {
process.env.PORT = originalEnvPort;
cleanupProject();
});
// TODO: Re-enable this test once https://github.com/pinojs/pino/issues/2253 is resolved
it.skip('it should generate an app that cosumes a non-buildable ts library', async () => {
const nodeapp = uniq('nodeapp');
const lib = uniq('lib');
const port = await reservePort();
process.env.PORT = `${port}`;
runCLI(
`generate @nx/node:app apps/${nodeapp} --port=${port} --bundler=esbuild --framework=fastify --no-interactive`
);
runCLI(
`generate @nx/js:lib packages/${lib} --bundler=none --e2eTestRunner=none --unitTestRunner=none`
);
updateFile(
`apps/${nodeapp}/src/main.ts`,
(content) => `import { ${names(lib).propertyName} } from '@proj/${lib}';
console.log(${names(lib).propertyName}());
${content}
`
);
// App is CJS by default so lets update the lib to follow the same pattern
updateJson(`packages/${lib}/tsconfig.lib.json`, (json) => {
json.compilerOptions.module = 'commonjs';
json.compilerOptions.moduleResolution = 'node';
return json;
});
updateJson('tsconfig.base.json', (json) => {
json.compilerOptions.moduleResolution = 'node';
json.compilerOptions.module = 'esnext';
delete json.compilerOptions.customConditions;
return json;
});
const pm = getSelectedPackageManager();
if (pm === 'pnpm') {
updateJson(`apps/${nodeapp}/package.json`, (json) => {
json.dependencies ??= {};
json.dependencies[`@proj/${lib}`] = 'workspace:*';
return json;
});
const pmc = getPackageManagerCommand({ packageManager: pm });
runCommand(pmc.install);
}
runCLI('sync');
// check build
expect(runCLI(`build ${nodeapp}`)).toContain(
`Successfully ran target build for project @proj/${nodeapp}`
);
});
});
+354
View File
@@ -0,0 +1,354 @@
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);
}
});
});
});
}
+122
View File
@@ -0,0 +1,122 @@
import {
checkFilesExist,
cleanupProject,
newProject,
readFile,
runCLI,
runCLIAsync,
runCommandUntil,
waitUntil,
tmpProjPath,
uniq,
updateFile,
updateJson,
promisifiedTreeKill,
} from '@nx/e2e-utils';
import { execSync } from 'child_process';
import { join } from 'path';
describe('Node Applications + webpack', () => {
beforeAll(() =>
newProject({
packages: ['@nx/node', '@nx/webpack', '@nx/esbuild'],
})
);
afterAll(() => cleanupProject());
it('should generate an app using webpack', async () => {
const app = uniq('nodeapp');
// This fails with Crystal enabled because `--optimization` is not a correct flag to pass to `webpack`.
runCLI(
`generate @nx/node:app apps/${app} --bundler=webpack --no-interactive --linter=eslint --unitTestRunner=jest`,
{
env: { NX_ADD_PLUGINS: 'false' },
}
);
checkFilesExist(`apps/${app}/webpack.config.js`);
updateFile(
`apps/${app}/src/main.ts`,
`
function foo(x: string) {
return "foo " + x;
};
console.log(foo("bar"));
`
);
await runCLIAsync(`build ${app}`);
checkFilesExist(`dist/apps/${app}/main.js`);
// no optimization by default
const content = readFile(`dist/apps/${app}/main.js`);
expect(content).toContain('console.log(foo("bar"))');
const result = execSync(`node dist/apps/${app}/main.js`, {
cwd: tmpProjPath(),
}).toString();
expect(result).toMatch(/foo bar/);
await runCLIAsync(`build ${app} --optimization`);
const optimizedContent = readFile(`dist/apps/${app}/main.js`);
expect(optimizedContent).toContain('console.log("foo "+"bar")');
// Test that serve can re-run dependency builds.
const lib = uniq('nodelib');
runCLI(
`generate @nx/js:lib libs/${lib} --bundler=esbuild --no-interactive`
);
updateJson(join('apps', app, 'project.json'), (config) => {
// Since we read from lib from dist, we should re-build it when lib changes.
config.targets.build.options.buildLibsFromSource = false;
config.targets.serve.options.runBuildTargetDependencies = true;
return config;
});
updateFile(
`apps/${app}/src/main.ts`,
`
import { ${lib} } from '@proj/${lib}';
console.log('Hello ' + ${lib}());
`
);
const serveProcess = await runCommandUntil(
`serve ${app} --watch --runBuildTargetDependencies`,
(output) => {
return output.includes(`Hello`);
},
{
env: {
NX_DAEMON: 'true',
},
}
);
// Update library source and check that it triggers rebuild.
const terminalOutputs: string[] = [];
serveProcess.stdout.on('data', (chunk) => {
const data = chunk.toString();
terminalOutputs.push(data);
});
updateFile(
`libs/${lib}/src/index.ts`,
`export function ${lib}() { return 'should rebuild lib'; }`
);
await waitUntil(
() => {
return terminalOutputs.some((output) =>
output.includes(`should rebuild lib`)
);
},
{ timeout: 60_000, ms: 200 }
);
await promisifiedTreeKill(serveProcess.pid, 'SIGKILL');
}, 300_000);
});
+777
View File
@@ -0,0 +1,777 @@
import { stripIndents } from '@angular-devkit/core/src/utils/literals';
import { joinPathFragments } from '@nx/devkit';
import {
checkFilesDoNotExist,
checkFilesExist,
cleanupProject,
createFile,
detectPackageManager,
getPackageManagerCommand,
killPorts,
newProject,
reservePort,
packageInstall,
packageManagerLockFile,
promisifiedTreeKill,
readFile,
runCLI,
runCLIAsync,
runCommand,
runCommandUntil,
tmpProjPath,
uniq,
updateFile,
updateJson,
} from '@nx/e2e-utils';
import { execSync } from 'child_process';
import * as http from 'http';
import { getLockFileName } from '@nx/js';
import { satisfies } from 'semver';
import { join } from 'path';
let originalEnvPort;
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);
}
});
});
});
}
describe('Node Applications', () => {
beforeAll(() => {
originalEnvPort = process.env.PORT;
newProject({
packages: [
'@nx/node',
'@nx/express',
'@nx/nest',
'@nx/webpack',
'@nx/jest',
],
});
});
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!');`);
await runCLIAsync(`build ${nodeapp}`);
checkFilesExist(`dist/apps/${nodeapp}/main.js`);
const result = execSync(`node dist/apps/${nodeapp}/main.js`, {
cwd: tmpProjPath(),
}).toString();
expect(result).toContain('Hello World!');
await killPorts(port);
}, 300000);
// TODO(crystal, @ndcunningham): This does not work because NxWebpackPlugin({}) outputFilename does not work.
xit('should be able to generate the correct outputFileName in options', async () => {
const nodeapp = uniq('nodeapp');
runCLI(
`generate @nx/node:app apps/${nodeapp} --linter=eslint --unitTestRunner=jest`
);
updateJson(join('apps', nodeapp, 'project.json'), (config) => {
config.targets.build.options.outputFileName = 'index.js';
return config;
});
await runCLIAsync(`build ${nodeapp}`);
checkFilesExist(`dist/apps/${nodeapp}/index.js`);
}, 300000);
it('should be able to generate an empty application with additional entries', async () => {
const nodeapp = uniq('nodeapp');
const port = await reservePort();
process.env.PORT = `${port}`;
runCLI(
`generate @nx/node:app apps/${nodeapp} --port=${port} --linter=eslint --bundler=webpack --unitTestRunner=jest`
);
const lintResults = runCLI(`lint ${nodeapp}`);
expect(lintResults).toContain('Successfully ran target lint');
updateFile(
`apps/${nodeapp}/webpack.config.js`,
`
const { NxAppWebpackPlugin } = require('@nx/webpack/app-plugin');
const { join } = require('path');
module.exports = {
output: {
path: join(__dirname, '../../dist/apps/${nodeapp}'),
},
plugins: [
new NxAppWebpackPlugin({
target: 'node',
compiler: 'tsc',
main: './src/main.ts',
tsConfig: './tsconfig.app.json',
assets: ['./src/assets'],
additionalEntryPoints: [
{
entryPath: 'apps/${nodeapp}/src/additional-main.ts',
entryName: 'additional-main',
}
],
optimization: false,
outputHashing: 'none',
}),
],
};
`
);
updateFile(
`apps/${nodeapp}/src/additional-main.ts`,
`console.log('Hello Additional World!');`
);
updateFile(
`apps/${nodeapp}/src/main.ts`,
`console.log('Hello World!');
console.log('env: ' + process.env['NODE_ENV']);
`
);
await runCLIAsync(`build ${nodeapp}`);
checkFilesExist(
`dist/apps/${nodeapp}/main.js`,
`dist/apps/${nodeapp}/additional-main.js`
);
const result = execSync(
`NODE_ENV=development && node dist/apps/${nodeapp}/main.js`,
{
cwd: tmpProjPath(),
}
).toString();
expect(result).toContain('Hello World!');
expect(result).toContain('env: development');
const additionalResult = execSync(
`node dist/apps/${nodeapp}/additional-main.js`,
{
cwd: tmpProjPath(),
}
).toString();
expect(additionalResult).toContain('Hello Additional World!');
await killPorts(port);
}, 300_000);
it('should be able to generate an empty application with variable in .env file', async () => {
const originalEnvPort = process.env.PORT;
const port = 3457;
process.env.PORT = `${port}`;
const nodeapp = uniq('nodeapp');
runCLI(
`generate @nx/node:app apps/${nodeapp} --linter=eslint --bundler=webpack --framework=none --unitTestRunner=jest`
);
updateFile('.env', `NX_FOOBAR="test foo bar"`);
updateFile(
`apps/${nodeapp}/src/main.ts`,
`console.log('foobar: ' + process.env['NX_FOOBAR']);`
);
await runCLIAsync(`build ${nodeapp}`);
checkFilesExist(`dist/apps/${nodeapp}/main.js`);
// check serving
const p = await runCommandUntil(
`serve ${nodeapp} --port=${port} --watch=false`,
(output) => {
process.stdout.write(output);
return output.includes(`foobar: test foo bar`);
},
{
timeout: 120000,
env: {
NX_DAEMON: 'true',
},
}
);
try {
await promisifiedTreeKill(p.pid, 'SIGKILL');
await killPorts(port);
} finally {
process.env.port = originalEnvPort;
}
}, 60000);
it("should exclude 'test' target from e2e project that uses jest", async () => {
const appName = uniq('nodeapp');
runCLI(
`generate @nx/node:app ${appName} --no-interactive --unitTestRunner=jest --linter=eslint --e2eTestRunner=jest`
);
const nxJson = JSON.parse(readFile('nx.json'));
expect(nxJson.plugins).toBeDefined();
const jestPlugin = nxJson.plugins.find(
(p) => p.plugin === '@nx/jest/plugin'
);
expect(jestPlugin).toBeDefined();
expect(jestPlugin.exclude).toContain(`${appName}-e2e/**/*`);
});
it('should be able to generate an express application', async () => {
const nodeapp = uniq('nodeapp');
const originalEnvPort = process.env.PORT;
const port = 3499;
process.env.PORT = `${port}`;
runCLI(
`generate @nx/express:app apps/${nodeapp} --port=${port} --linter=eslint --unitTestRunner=jest`
);
const lintResults = runCLI(`lint ${nodeapp}`);
expect(lintResults).toContain('Successfully ran target lint');
updateFile(
`apps/${nodeapp}/src/app/test.spec.ts`,
`
describe('test', () => {
it('should work', () => {
expect(true).toEqual(true);
})
})
`
);
const jestResult = runCLI(`test ${nodeapp}`);
expect(jestResult).toContain('Successfully ran target test');
// checking serve
updateFile(`apps/${nodeapp}/src/assets/file.txt`, `Test`);
const p = await runCommandUntil(
`serve ${nodeapp}`,
(output) => output.includes(`Listening at http://localhost:${port}`),
{
timeout: 120000,
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');
await killPorts(port);
} catch {
// do nothing
}
}, 120_000);
it('should be able to generate a nest application', async () => {
const nestapp = uniq('nestapp');
const port = 3335;
runCLI(
`generate @nx/nest:app apps/${nestapp} --linter=eslint --unitTestRunner=jest`
);
const lintResults = runCLI(`lint ${nestapp}`);
expect(lintResults).toContain('Successfully ran target lint');
updateFile(`apps/${nestapp}/src/assets/file.txt`, ``);
const jestResult = await runCLIAsync(`test ${nestapp}`);
expect(jestResult.combinedOutput).toContain(
'Test Suites: 2 passed, 2 total'
);
const buildResult = runCLI(`build ${nestapp}`);
checkFilesExist(
`dist/apps/${nestapp}/main.js`,
`dist/apps/${nestapp}/assets/file.txt`
);
expect(buildResult).toContain(
`Successfully ran target build for project ${nestapp}`
);
// checking serve
const p = await runCommandUntil(
`serve ${nestapp} --port=${port}`,
(output) => {
process.stdout.write(output);
return output.includes(`listening on ws://localhost:${port}`);
},
{
timeout: 120000,
env: {
NX_DAEMON: 'true',
},
}
);
await killPorts(port);
await promisifiedTreeKill(p.pid, 'SIGKILL');
const e2eRsult = await runCLIAsync(`e2e ${nestapp}-e2e`);
expect(e2eRsult.combinedOutput).toContain('Test Suites: 1 passed, 1 total');
}, 120000);
it('should generate a nest application with docker', async () => {
const nestapp = 'node-nest-docker-test';
runCLI(
`generate @nx/node:app ${nestapp} --bundler=webpack --framework=nest --docker --unitTestRunner=jest`
);
checkFilesExist(`${nestapp}/Dockerfile`);
});
// TODO(crystal, @ndcunningham): how do we handle this now?
// Revisit when NxWebpackPlugin({}) outputFilename is working.
xit('should be able to run ESM applications', async () => {
const esmapp = uniq('esmapp');
runCLI(
`generate @nx/node:app ${esmapp} --linter=eslint --framework=none --bundler=webpack --unitTestRunner=jest`
);
updateJson(`apps/${esmapp}/tsconfig.app.json`, (config) => {
config.module = 'esnext';
config.target = 'es2020';
return config;
});
updateJson(join('apps', esmapp, 'project.json'), (config) => {
config.targets.build.options.outputFileName = 'main.mjs';
config.targets.build.options.assets = [];
return config;
});
updateFile(
`apps/${esmapp}/webpack.config.js`,
`
const { composePlugins, withNx } = require('@nx/webpack');
module.exports = composePlugins(withNx(), (config) => {
config.experiments = {
...config.experiments,
outputModule: true,
topLevelAwait: true,
};
config.output = {
path: config.output.path,
chunkFormat: 'module',
library: { type: 'module' }
}
return config;
});
`
);
await runCLIAsync(`build ${esmapp}`);
const p = await runCommandUntil(
`serve ${esmapp}`,
(output) => {
return output.includes('Hello World');
},
{
timeout: 120000,
env: {
NX_DAEMON: 'true',
},
}
);
await promisifiedTreeKill(p.pid, 'SIGKILL');
}, 300000);
});
describe('Build Node apps', () => {
let scope: string;
beforeAll(() => {
originalEnvPort = process.env.PORT;
scope = newProject({
packages: [
'@nx/node',
'@nx/express',
'@nx/nest',
'@nx/webpack',
'@nx/jest',
],
});
});
afterAll(() => {
process.env.PORT = originalEnvPort;
cleanupProject();
});
// TODO(crystal, @ndcunningham): What is the alternative here?
xit('should generate a package.json with the `--generatePackageJson` flag', async () => {
const packageManager = detectPackageManager(tmpProjPath());
const nestapp = uniq('nestapp');
runCLI(
`generate @nx/nest:app apps/${nestapp} --linter=eslint --unitTestRunner=jest`
);
await runCLIAsync(`build ${nestapp} --generatePackageJson`);
checkFilesExist(`dist/apps/${nestapp}/package.json`);
checkFilesExist(
`dist/apps/${nestapp}/${getLockFileName(
detectPackageManager(tmpProjPath())
)}`
);
const rootPackageJson = JSON.parse(readFile(`package.json`));
const packageJson = JSON.parse(
readFile(`dist/apps/${nestapp}/package.json`)
);
expect(packageJson).toEqual(
expect.objectContaining({
main: 'main.js',
name: expect.any(String),
version: '0.0.1',
})
);
expect(
satisfies(
packageJson.dependencies['@nestjs/common'],
rootPackageJson.dependencies['@nestjs/common']
)
).toBeTruthy();
expect(
satisfies(
packageJson.dependencies['@nestjs/core'],
rootPackageJson.dependencies['@nestjs/core']
)
).toBeTruthy();
expect(
satisfies(
packageJson.dependencies['reflect-metadata'],
rootPackageJson.dependencies['reflect-metadata']
)
).toBeTruthy();
expect(
satisfies(
packageJson.dependencies['rxjs'],
rootPackageJson.dependencies['rxjs']
)
).toBeTruthy();
expect(
satisfies(
packageJson.dependencies['tslib'],
rootPackageJson.dependencies['tslib']
)
).toBeTruthy();
checkFilesExist(
`dist/apps/${nestapp}/${packageManagerLockFile[packageManager]}`
);
runCommand(`${getPackageManagerCommand().ciInstall}`, {
cwd: joinPathFragments(tmpProjPath(), 'dist/apps', nestapp),
});
const nodeapp = uniq('nodeapp');
runCLI(
`generate @nx/node:app ${nodeapp} --bundler=webpack --unitTestRunner=jest --linter=eslint`
);
const jslib = uniq('jslib');
runCLI(`generate @nx/js:lib ${jslib} --bundler=tsc`);
updateFile(
`apps/${nodeapp}/src/main.ts`,
`
import { ${jslib} } from '@${scope}/${jslib}';
console.log('Hello World!');
${jslib}();
`
);
const { combinedOutput: nodeCombinedOutput } = await runCLIAsync(
`build ${nodeapp} --generate-package-json`
);
expect(nodeCombinedOutput).not.toMatch(/Graph is not consistent/);
checkFilesExist(`dist/apps/${nodeapp}/package.json`);
checkFilesExist(
`dist/apps/${nodeapp}/${packageManagerLockFile[packageManager]}`
);
const nodeAppPackageJson = JSON.parse(
readFile(`dist/apps/${nodeapp}/package.json`)
);
expect(nodeAppPackageJson['dependencies']['tslib']).toBeTruthy();
runCommand(`${getPackageManagerCommand().ciInstall}`, {
cwd: joinPathFragments(tmpProjPath(), 'dist/apps', nestapp),
});
runCommand(`${getPackageManagerCommand().ciInstall}`, {
cwd: joinPathFragments(tmpProjPath(), 'dist/apps', nodeapp),
});
}, 1_000_000);
it('should remove previous output before building with the --deleteOutputPath option set', async () => {
const appName = uniq('app');
const port = await reservePort();
process.env.PORT = `${port}`;
runCLI(
`generate @nx/node:app apps/${appName} --port=${port} --no-interactive --linter=eslint --unitTestRunner=jest`
);
// deleteOutputPath should default to true
createFile(`dist/apps/${appName}/_should_remove.txt`);
createFile(`dist/apps/_should_not_remove.txt`);
checkFilesExist(
`dist/apps/${appName}/_should_remove.txt`,
`dist/apps/_should_not_remove.txt`
);
runCLI(`build ${appName} --outputHashing none`); // no explicit deleteOutputPath option set
checkFilesDoNotExist(`dist/apps/${appName}/_should_remove.txt`);
checkFilesExist(`dist/apps/_should_not_remove.txt`);
}, 120000);
it('should support generating projects with the new name and root format', async () => {
const appName = uniq('app1');
const libName = uniq('@my-org/lib1');
const port = await reservePort();
process.env.PORT = `${port}`;
runCLI(
`generate @nx/node:app ${appName} --port=${port} --no-interactive --linter=eslint --unitTestRunner=jest`
);
// check files are generated without the layout directory ("apps/") and
// using the project name as the directory when no directory is provided
checkFilesExist(`${appName}/src/main.ts`);
// check build works
expect(runCLI(`build ${appName}`)).toContain(
`Successfully ran target build for project ${appName}`
);
// check tests pass
const appTestResult = runCLI(`test ${appName} --passWithNoTests`);
expect(appTestResult).toContain(
`Successfully ran target test for project ${appName}`
);
runCLI(
`generate @nx/node:lib ${libName} --buildable --no-interactive --linter=eslint --unitTestRunner=jest`
);
// check files are generated without the layout directory ("libs/") and
// using the project name as the directory when no directory is provided
checkFilesExist(`${libName}/src/index.ts`);
// check build works
expect(runCLI(`build ${libName}`)).toContain(
`Successfully ran target build for project ${libName}`
);
// check tests pass
const libTestResult = runCLI(`test ${libName}`);
expect(libTestResult).toContain(
`Successfully ran target test for project ${libName}`
);
}, 500_000);
// TODO(crystal, @ndcunningnam): Investigate why these tests are failing
xdescribe('NestJS', () => {
// TODO(crystal, @ndcunningham): What is the alternative here?
xit('should have plugin output if specified in `tsPlugins`', async () => {
const nestapp = uniq('nestapp');
runCLI(
`generate @nx/nest:app ${nestapp} --linter=eslint --unitTestRunner=jest`
);
packageInstall('@nestjs/swagger', undefined, '^11.0.0');
updateJson(join('apps', nestapp, 'project.json'), (config) => {
config.targets.build.options.tsPlugins = ['@nestjs/swagger/plugin'];
return config;
});
updateFile(
`apps/${nestapp}/src/app/foo.dto.ts`,
`
export class FooDto {
foo: string;
bar: number;
}`
);
updateFile(
`apps/${nestapp}/src/app/app.controller.ts`,
`
import { Controller, Get } from '@nestjs/common';
import { FooDto } from './foo.dto';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getData() {
return this.appService.getData();
}
@Get('foo')
getFoo(): Promise<FooDto> {
return Promise.resolve({
foo: 'foo',
bar: 123
})
}
}`
);
await runCLIAsync(`build ${nestapp}`);
const mainJs = readFile(`dist/apps/${nestapp}/main.js`);
expect(mainJs).toContain('FooDto');
expect(mainJs).toContain('_OPENAPI_METADATA_FACTORY');
}, 300000);
});
describe('nest libraries', function () {
it('should be able to generate a nest library', async () => {
const nestlib = uniq('nestlib');
runCLI(
`generate @nx/nest:lib libs/${nestlib} --linter=eslint --unitTestRunner=jest`
);
const lintResults = runCLI(`lint ${nestlib}`);
expect(lintResults).toContain('Successfully ran target lint');
const testResults = runCLI(`test ${nestlib} --passWithNoTests`);
expect(testResults).toContain(
`Successfully ran target test for project ${nestlib}`
);
}, 60000);
it('should be able to generate a nest library w/ service', async () => {
const nestlib = uniq('nestlib');
runCLI(
`generate @nx/nest:lib ${nestlib} --service --linter=eslint --unitTestRunner=jest`
);
const lintResults = runCLI(`lint ${nestlib}`);
expect(lintResults).toContain('Successfully ran target lint');
const jestResult = await runCLIAsync(`test ${nestlib}`);
expect(jestResult.combinedOutput).toContain(
'Test Suites: 1 passed, 1 total'
);
}, 200000);
it('should be able to generate a nest library w/ controller', async () => {
const nestlib = uniq('nestlib');
runCLI(
`generate @nx/nest:lib ${nestlib} --controller --linter=eslint --unitTestRunner=jest`
);
const lintResults = runCLI(`lint ${nestlib}`);
expect(lintResults).toContain('Successfully ran target lint');
const jestResult = await runCLIAsync(`test ${nestlib}`);
expect(jestResult.combinedOutput).toContain(
'Test Suites: 1 passed, 1 total'
);
}, 200000);
it('should be able to generate a nest library w/ controller and service', async () => {
const nestlib = uniq('nestlib');
runCLI(
`generate @nx/nest:lib ${nestlib} --controller --service --linter=eslint --unitTestRunner=jest`
);
const lintResults = runCLI(`lint ${nestlib}`);
expect(lintResults).toContain('Successfully ran target lint');
const jestResult = await runCLIAsync(`test ${nestlib}`);
expect(jestResult.combinedOutput).toContain(
'Test Suites: 2 passed, 2 total'
);
}, 200000);
it('should have plugin output if specified in `transformers`', async () => {
const nestlib = uniq('nestlib');
runCLI(
`generate @nx/nest:lib libs/${nestlib} --buildable --linter=eslint --unitTestRunner=jest`
);
packageInstall('@nestjs/swagger', undefined, '^11.0.0');
updateJson(join('libs', nestlib, 'project.json'), (config) => {
config.targets.build.options.transformers = [
{
name: '@nestjs/swagger/plugin',
options: {
dtoFileNameSuffix: ['.model.ts'],
},
},
];
return config;
});
updateFile(
`libs/${nestlib}/src/lib/foo.model.ts`,
`
export class FooModel {
foo?: string;
bar?: number;
}`
);
await runCLIAsync(`build ${nestlib}`);
const fooModelJs = readFile(`dist/libs/${nestlib}/src/lib/foo.model.js`);
expect(stripIndents`${fooModelJs}`).toContain(
stripIndents`
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.FooModel = void 0;
const openapi = require("@nestjs/swagger");
class FooModel {
static _OPENAPI_METADATA_FACTORY() {
return { foo: { required: false, type: () => String }, bar: { required: false, type: () => Number } };
}
}
exports.FooModel = FooModel;
//# sourceMappingURL=foo.model.js.map
`
);
}, 300000);
});
});