From c1ede83d62b5fad5b7322f2ca9ef1a238cc32705 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Jona=C5=A1?= Date: Mon, 5 Feb 2024 14:40:54 +0100 Subject: [PATCH 01/26] chore(linter): fix and reenable disabled eslint test (#21601) --- e2e/eslint/src/linter.test.ts | 41 +++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/e2e/eslint/src/linter.test.ts b/e2e/eslint/src/linter.test.ts index 4c4eb6dadb2dc..f94fe99ae9c9f 100644 --- a/e2e/eslint/src/linter.test.ts +++ b/e2e/eslint/src/linter.test.ts @@ -248,9 +248,10 @@ describe('Linter', () => { const libC = uniq('tslib-c'); beforeAll(() => { - runCLI(`generate @nx/js:lib ${libA}`); - runCLI(`generate @nx/js:lib ${libB}`); - runCLI(`generate @nx/js:lib ${libC}`); + // make these libs non-buildable to avoid dep-checks triggering lint errors + runCLI(`generate @nx/js:lib ${libA} --bundler=none`); + runCLI(`generate @nx/js:lib ${libB} --bundler=none`); + runCLI(`generate @nx/js:lib ${libC} --bundler=none`); /** * create tslib-a structure @@ -402,8 +403,7 @@ describe('Linter', () => { ); }); - // TODO(crystal, @meeroslav): Investigate why this is failing - xit('should fix noRelativeOrAbsoluteImportsAcrossLibraries', () => { + it('should fix noRelativeOrAbsoluteImportsAcrossLibraries', () => { const stdout = runCLI(`lint ${libB}`, { silenceError: true, }); @@ -435,16 +435,29 @@ describe('Linter', () => { describe('dependency checks', () => { beforeAll(() => { updateJson(`libs/${mylib}/.eslintrc.json`, (json) => { - json.overrides = [ - ...json.overrides, - { - files: ['*.json'], - parser: 'jsonc-eslint-parser', - rules: { - '@nx/dependency-checks': 'error', + if (!json.overrides.some((o) => o.rules?.['@nx/dependency-checks'])) { + json.overrides = [ + ...json.overrides, + { + files: ['*.json'], + parser: 'jsonc-eslint-parser', + rules: { + '@nx/dependency-checks': 'error', + }, }, - }, - ]; + ]; + } + return json; + }); + }); + + afterAll(() => { + // ensure the rule for dependency checks is removed + // so that it does not affect other tests + updateJson(`libs/${mylib}/.eslintrc.json`, (json) => { + json.overrides = json.overrides.filter( + (o) => !o.rules?.['@nx/dependency-checks'] + ); return json; }); }); From a5246650730de95bc4081de1f86e8fee6aa30e72 Mon Sep 17 00:00:00 2001 From: Jack Hsu Date: Mon, 5 Feb 2024 08:57:18 -0500 Subject: [PATCH 02/26] chore(nextjs): remove tests that are unnecessary (#21566) --- e2e/next-extensions/src/utils.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/e2e/next-extensions/src/utils.ts b/e2e/next-extensions/src/utils.ts index 25fd6e2ac8abf..c5aedb7d30c4d 100644 --- a/e2e/next-extensions/src/utils.ts +++ b/e2e/next-extensions/src/utils.ts @@ -34,12 +34,6 @@ export async function checkApp( expect(buildResult).toContain(`Successfully ran target build`); checkFilesExist(`${appsDir}/${appName}/.next/build-manifest.json`); - // TODO(crystal, @ndcunningham): Investigate if this file is correct - // const packageJson = readJson(`${appsDir}/${appName}/.next/package.json`); - // expect(packageJson.dependencies.react).toBeDefined(); - // expect(packageJson.dependencies['react-dom']).toBeDefined(); - // expect(packageJson.dependencies.next).toBeDefined(); - if (opts.checkE2E && runE2ETests()) { const e2eResults = runCLI( `e2e ${appName}-e2e --no-watch --configuration=production` From 48296b043d41a46216a5652ec8b42ce7ad601a92 Mon Sep 17 00:00:00 2001 From: Pascal Brewing Date: Mon, 5 Feb 2024 15:26:29 +0100 Subject: [PATCH 03/26] docs(core): add analogjs nx plugin to approved-plugins.json (#21080) Co-authored-by: PASCAL BREWING --- community/approved-plugins.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/community/approved-plugins.json b/community/approved-plugins.json index c4867a26b3ae2..dfeb867a6f580 100644 --- a/community/approved-plugins.json +++ b/community/approved-plugins.json @@ -458,5 +458,10 @@ "name": "@gnuechtel/nx-cucumber", "description": "Plugin to use Cucumber within Nx workspaces", "url": "https://gitlab.com/gnuechtel/open-source/-/tree/main/libs/nx-cucumber" + }, + { + "name": "@analogjs/platform", + "description": "Official plugin to add Analog to your Nx monorepo.", + "url": "https://analogjs.org/docs/integrations/nx" } ] From 04211785e85b864f34d152a3f9c4c312ef4b3d21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leosvel=20P=C3=A9rez=20Espinosa?= Date: Mon, 5 Feb 2024 15:33:40 +0100 Subject: [PATCH 04/26] fix(angular): fix wrong trailing comma in mf bootstrap code generation (#21600) --- .../generators/setup-mf/__snapshots__/setup-mf.spec.ts.snap | 4 ++-- .../angular/src/generators/setup-mf/lib/fix-bootstrap.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/angular/src/generators/setup-mf/__snapshots__/setup-mf.spec.ts.snap b/packages/angular/src/generators/setup-mf/__snapshots__/setup-mf.spec.ts.snap index 82a3007966170..0fbe81c63e318 100644 --- a/packages/angular/src/generators/setup-mf/__snapshots__/setup-mf.spec.ts.snap +++ b/packages/angular/src/generators/setup-mf/__snapshots__/setup-mf.spec.ts.snap @@ -6,7 +6,7 @@ exports[`Init MF --federationType=dynamic should create a host with the correct fetch('/assets/module-federation.manifest.json') .then((res) => res.json()) .then(definitions => setRemoteDefinitions(definitions)) - .then(() => import('./bootstrap').catch(err => console.error(err));)" + .then(() => import('./bootstrap').catch(err => console.error(err)));" `; exports[`Init MF --federationType=dynamic should create a host with the correct configurations when --typescriptConfiguration=true 1`] = ` @@ -15,7 +15,7 @@ exports[`Init MF --federationType=dynamic should create a host with the correct fetch('/assets/module-federation.manifest.json') .then((res) => res.json()) .then(definitions => setRemoteDefinitions(definitions)) - .then(() => import('./bootstrap').catch(err => console.error(err));)" + .then(() => import('./bootstrap').catch(err => console.error(err)));" `; exports[`Init MF should add a remote application and add it to a specified host applications router config 1`] = ` diff --git a/packages/angular/src/generators/setup-mf/lib/fix-bootstrap.ts b/packages/angular/src/generators/setup-mf/lib/fix-bootstrap.ts index 1394f82edcedd..78246735d822d 100644 --- a/packages/angular/src/generators/setup-mf/lib/fix-bootstrap.ts +++ b/packages/angular/src/generators/setup-mf/lib/fix-bootstrap.ts @@ -11,20 +11,20 @@ export function fixBootstrap(tree: Tree, appRoot: string, options: Schema) { tree.write(joinPathFragments(appRoot, 'src/bootstrap.ts'), bootstrapCode); } - const bootstrapImportCode = `import('./bootstrap').catch(err => console.error(err));`; + const bootstrapImportCode = `import('./bootstrap').catch(err => console.error(err))`; const fetchMFManifestCode = `import { setRemoteDefinitions } from '@nx/angular/mf'; fetch('/assets/module-federation.manifest.json') .then((res) => res.json()) .then(definitions => setRemoteDefinitions(definitions)) - .then(() => ${bootstrapImportCode})`; + .then(() => ${bootstrapImportCode});`; tree.write( mainFilePath, options.mfType === 'host' && options.federationType === 'dynamic' ? fetchMFManifestCode - : bootstrapImportCode + : `${bootstrapImportCode};` ); } From a02af1615a5a803a13c5b8a1d71fa267b96a54c7 Mon Sep 17 00:00:00 2001 From: "Laurence \"DC5B\" Lord" Date: Mon, 5 Feb 2024 15:18:06 +0000 Subject: [PATCH 05/26] Correct typo (#21597) Co-authored-by: Isaac Mann --- docs/generated/packages/next/documents/overview.md | 2 +- docs/shared/packages/next/plugin-overview.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/generated/packages/next/documents/overview.md b/docs/generated/packages/next/documents/overview.md index 012d307e277dd..16d74612a48dc 100644 --- a/docs/generated/packages/next/documents/overview.md +++ b/docs/generated/packages/next/documents/overview.md @@ -238,7 +238,7 @@ The library in `dist` is publishable to npm or a private registry. ### Static HTML Export -Next.js applications can be statically exported by changing th eoutput inside your Next.js configuration file. +Next.js applications can be statically exported by changing the output inside your Next.js configuration file. ```js {% fileName="apps/my-next-app/next.config.js" %} const nextConfig = { diff --git a/docs/shared/packages/next/plugin-overview.md b/docs/shared/packages/next/plugin-overview.md index 012d307e277dd..16d74612a48dc 100644 --- a/docs/shared/packages/next/plugin-overview.md +++ b/docs/shared/packages/next/plugin-overview.md @@ -238,7 +238,7 @@ The library in `dist` is publishable to npm or a private registry. ### Static HTML Export -Next.js applications can be statically exported by changing th eoutput inside your Next.js configuration file. +Next.js applications can be statically exported by changing the output inside your Next.js configuration file. ```js {% fileName="apps/my-next-app/next.config.js" %} const nextConfig = { From a6ddf082014d3e2db6259fe120efae613beeb9ac Mon Sep 17 00:00:00 2001 From: Philip Fulcher Date: Mon, 5 Feb 2024 08:41:04 -0700 Subject: [PATCH 06/26] docs(nx-dev): add launch nx announcements template (#21557) Co-authored-by: Juri --- .../images/launch-nx/proj-crystal-launch.jpg | Bin 0 -> 42213 bytes .../src/lib/launch-week/announcements.tsx | 80 +++++++++++++++--- 2 files changed, 70 insertions(+), 10 deletions(-) create mode 100644 nx-dev/nx-dev/public/images/launch-nx/proj-crystal-launch.jpg diff --git a/nx-dev/nx-dev/public/images/launch-nx/proj-crystal-launch.jpg b/nx-dev/nx-dev/public/images/launch-nx/proj-crystal-launch.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c297847ce0752e7001c0f95b6be083bcedf2d268 GIT binary patch literal 42213 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 literal 0 HcmV?d00001 diff --git a/nx-dev/ui-conference/src/lib/launch-week/announcements.tsx b/nx-dev/ui-conference/src/lib/launch-week/announcements.tsx index 5bf924cb04f94..1cbc82a5e932b 100644 --- a/nx-dev/ui-conference/src/lib/launch-week/announcements.tsx +++ b/nx-dev/ui-conference/src/lib/launch-week/announcements.tsx @@ -1,15 +1,75 @@ +import { ButtonLink, SectionHeading } from '@nx/nx-dev/ui-common'; + export function LaunchWeekAnnouncements(): JSX.Element { return ( -

-
-
-
-
-

- We’ll be sharing new features and content daily during launch - week, so be sure to keep an eye on this space for all the latest - info! -

+
+
+
+
+

+ We’ll be sharing new features and content daily during launch + week, so be sure to keep an eye on this space for all the latest + info! +

+
+
+ + {/* MONDAY */} +
+
+
+
+
+ + Monday + + + Announcing Project Crystal + +
+
+

+ When working on the next iteration of Nx, one idea + consistently emerged: Nx Plugins are powerful and have + proven to help large enterprises adopt monorepos, + successfully maintaining and scaling them. However, there's + definitely a barrier to entry. So, what if Nx Plugins + functioned more like VSCode extensions? You simply add them, + and they instantly enhance the experience of working with a + given tool or technology. +
+ This is what Nx Project Crystal is all about. +

+
+
+ + Read the blog post + + + Watch the video + +
+
+
From 140d22f7a3cf8a4e699d78cd1b36218a631914b4 Mon Sep 17 00:00:00 2001 From: Isaac Mann Date: Mon, 5 Feb 2024 11:15:28 -0500 Subject: [PATCH 07/26] docs(core): project crystal video (#21605) --- docs/shared/concepts/inferred-tasks.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/shared/concepts/inferred-tasks.md b/docs/shared/concepts/inferred-tasks.md index 98135d3080dfe..3d81e31ea4396 100644 --- a/docs/shared/concepts/inferred-tasks.md +++ b/docs/shared/concepts/inferred-tasks.md @@ -1,9 +1,14 @@ -# Inferred Tasks +# Inferred Tasks (Project Crystal) In Nx version 18, many Nx plugins will automatically infer tasks for your projects based on the configuration of different tools. Many tools have configuration files which determine what a tool does. Nx is able to cache the results of running the tool. Nx plugins use the same configuration files to infer how Nx should [run the task](/features/run-tasks). This includes [fine-tuned cache settings](/features/cache-task-results) and automatic [task dependencies](/concepts/task-pipeline-configuration). For example, the `@nx/webpack` plugin infers tasks to run webpack through Nx based on your repository's webpack configuration. This configuration already defines the destination of your build files, so Nx reads that value and caches the correct output files. +{% youtube +src="https://youtu.be/wADNsVItnsM" +title="Project Crystal" +/%} + ## How Does a Plugin Infer Tasks? Every plugin has its own custom logic, but in order to infer tasks, they all go through the following steps. From 44d33c243a03c11f89447ab088de66c3bec899b6 Mon Sep 17 00:00:00 2001 From: Jack Hsu Date: Mon, 5 Feb 2024 11:40:26 -0500 Subject: [PATCH 08/26] fix(nextjs): move `next/constants` from top-level import to when it is needed (#21612) --- packages/next/src/plugins/plugin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next/src/plugins/plugin.ts b/packages/next/src/plugins/plugin.ts index b1a6ae76ff754..dcc2d5c1e140c 100644 --- a/packages/next/src/plugins/plugin.ts +++ b/packages/next/src/plugins/plugin.ts @@ -15,7 +15,6 @@ import { existsSync, readdirSync } from 'fs'; import { projectGraphCacheDirectory } from 'nx/src/utils/cache-directory'; import { calculateHashForCreateNodes } from '@nx/devkit/src/utils/calculate-hash-for-create-nodes'; -import { PHASE_PRODUCTION_BUILD } from 'next/constants'; import { getLockFileName } from '@nx/js'; export interface NextPluginOptions { @@ -155,6 +154,7 @@ function getStartTargetConfig(options: NextPluginOptions, projectRoot: string) { async function getOutputs(projectRoot, nextConfig) { let dir = '.next'; + const { PHASE_PRODUCTION_BUILD } = require('next/constants'); if (typeof nextConfig === 'function') { // Works for both async and sync functions. From b97958c1b3dff725c87002a5b3bf9c7dd3c2d516 Mon Sep 17 00:00:00 2001 From: Jack Hsu Date: Mon, 5 Feb 2024 11:52:10 -0500 Subject: [PATCH 09/26] fix(core): pass the full resolved path of ts-node/esm when reloading the CLI (#21607) --- packages/nx/bin/init-local.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nx/bin/init-local.ts b/packages/nx/bin/init-local.ts index 6a82c9bbaebd4..94930c59d5cdf 100644 --- a/packages/nx/bin/init-local.ts +++ b/packages/nx/bin/init-local.ts @@ -236,7 +236,7 @@ function execArgvWithExperimentalLoaderOptions() { ...process.execArgv, '--no-warnings', '--experimental-loader', - 'ts-node/esm', + require.resolve('ts-node/esm'), ]; } From f38ad96ec565e9ef8522ede4359d3526218326f5 Mon Sep 17 00:00:00 2001 From: Rares Matei Date: Mon, 5 Feb 2024 17:15:21 +0000 Subject: [PATCH 10/26] chore(repo): install lsof and other e2e deps (#21615) --- .nx/workflows/agents.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.nx/workflows/agents.yaml b/.nx/workflows/agents.yaml index e4c5dc8443016..f73921e1bfc82 100644 --- a/.nx/workflows/agents.yaml +++ b/.nx/workflows/agents.yaml @@ -21,7 +21,10 @@ launch-templates: ~/.cache/Cypress ~/.pnpm-store BASE_BRANCH: 'master' - + - name: Install e2e deps + script: | + sudo apt-get update + sudo apt-get install -y ca-certificates lsof - name: Install Pnpm script: | npm install -g @pnpm/exe@8.7.4 From 371a1ed6b8f6736f6cede6e98f5220a80b93a673 Mon Sep 17 00:00:00 2001 From: Nicholas Cunningham Date: Mon, 5 Feb 2024 10:16:08 -0700 Subject: [PATCH 11/26] fix(node): Broken E2E tests (#21569) --- e2e/node/src/node-esbuild.test.ts | 1 - e2e/node/src/node-server.test.ts | 4 +- e2e/node/src/node.test.ts | 100 ++++++++++++++++++++++-------- 3 files changed, 77 insertions(+), 28 deletions(-) diff --git a/e2e/node/src/node-esbuild.test.ts b/e2e/node/src/node-esbuild.test.ts index 666975dd73b57..2ca4c99faa222 100644 --- a/e2e/node/src/node-esbuild.test.ts +++ b/e2e/node/src/node-esbuild.test.ts @@ -10,7 +10,6 @@ import { uniq, updateFile, } from '@nx/e2e/utils'; -import { join } from 'path'; describe('Node Applications + esbuild', () => { beforeAll(() => diff --git a/e2e/node/src/node-server.test.ts b/e2e/node/src/node-server.test.ts index 4abfcd7f432cc..8c642109c51fb 100644 --- a/e2e/node/src/node-server.test.ts +++ b/e2e/node/src/node-server.test.ts @@ -62,7 +62,9 @@ describe('Node Applications + webpack', () => { process.env.PORT = ''; } - it('should generate an app using webpack', async () => { + // 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 () => { const testLib1 = uniq('test1'); const testLib2 = uniq('test2'); const expressApp = uniq('expressapp'); diff --git a/e2e/node/src/node.test.ts b/e2e/node/src/node.test.ts index 486ef9aaeab69..95e0068f65141 100644 --- a/e2e/node/src/node.test.ts +++ b/e2e/node/src/node.test.ts @@ -29,6 +29,12 @@ import { getLockFileName } from '@nx/js'; import { satisfies } from 'semver'; import { join } from 'path'; +let originalEnvPort; + +function getRandomPort() { + return Math.floor(1000 + Math.random() * 9000); +} + function getData(port, path = '/api'): Promise { return new Promise((resolve) => { http.get(`http://localhost:${port}${path}`, (res) => { @@ -49,18 +55,23 @@ function getData(port, path = '/api'): Promise { } describe('Node Applications', () => { - beforeAll(() => + beforeAll(() => { + originalEnvPort = process.env.PORT; newProject({ packages: ['@nx/node', '@nx/express', '@nx/nest'], - }) - ); + }); + }); - afterAll(() => cleanupProject()); + afterAll(() => { + process.env.PORT = originalEnvPort; + cleanupProject(); + }); it('should be able to generate an empty application', async () => { const nodeapp = uniq('nodeapp'); - - runCLI(`generate @nx/node:app ${nodeapp} --linter=eslint`); + const port = getRandomPort(); + process.env.PORT = `${port}`; + runCLI(`generate @nx/node:app ${nodeapp} --port=${port} --linter=eslint`); const lintResults = runCLI(`lint ${nodeapp}`); expect(lintResults).toContain('Successfully ran target lint'); @@ -73,9 +84,10 @@ describe('Node Applications', () => { cwd: tmpProjPath(), }).toString(); expect(result).toContain('Hello World!'); + await killPorts(port); }, 300000); - // TODO(crystal, @ndcunningham): What is the alternative here? + // 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 ${nodeapp} --linter=eslint`); @@ -89,26 +101,47 @@ describe('Node Applications', () => { checkFilesExist(`dist/apps/${nodeapp}/index.js`); }, 300000); - // TODO(crystal, @ndcunningham): What is the alternative here? - xit('should be able to generate an empty application with additional entries', async () => { + it('should be able to generate an empty application with additional entries', async () => { const nodeapp = uniq('nodeapp'); - + const port = getRandomPort(); + process.env.PORT = `${port}`; runCLI( - `generate @nx/node:app ${nodeapp} --linter=eslint --bundler=webpack` + `generate @nx/node:app ${nodeapp} --port=${port} --linter=eslint --bundler=webpack` ); const lintResults = runCLI(`lint ${nodeapp}`); expect(lintResults).toContain('Successfully ran target lint'); - updateJson(join('apps', nodeapp, 'project.json'), (config) => { - config.targets.build.options.additionalEntryPoints = [ + updateFile( + `apps/${nodeapp}/webpack.config.js`, + ` +const { NxWebpackPlugin } = require('@nx/webpack'); +const { join } = require('path'); + +module.exports = { + output: { + path: join(__dirname, '../../dist/apps/${nodeapp}'), + }, + plugins: [ + new NxWebpackPlugin({ + 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', - entryPath: `apps/${nodeapp}/src/additional-main.ts`, - }, - ]; - return config; - }); + } + ], + optimization: false, + outputHashing: 'none', + }), + ], +}; + ` + ); updateFile( `apps/${nodeapp}/src/additional-main.ts`, @@ -144,6 +177,8 @@ describe('Node Applications', () => { } ).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 () => { @@ -185,10 +220,12 @@ describe('Node Applications', () => { it('should be able to generate an express application', async () => { const nodeapp = uniq('nodeapp'); const originalEnvPort = process.env.PORT; - const port = 3456; + const port = 3499; process.env.PORT = `${port}`; - runCLI(`generate @nx/express:app ${nodeapp} --linter=eslint`); + runCLI( + `generate @nx/express:app ${nodeapp} --port=${port} --linter=eslint` + ); const lintResults = runCLI(`lint ${nodeapp}`); expect(lintResults).toContain('Successfully ran target lint'); @@ -221,9 +258,9 @@ describe('Node Applications', () => { try { await promisifiedTreeKill(p.pid, 'SIGKILL'); - await killPorts(port); - } finally { - process.env.port = originalEnvPort; + expect(await killPorts(port)).toBeTruthy(); + } catch (err) { + expect(err).toBeFalsy(); } }, 120_000); @@ -287,6 +324,7 @@ describe('Node Applications', () => { }, 120000); // 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'); @@ -333,12 +371,16 @@ describe('Node Applications', () => { describe('Build Node apps', () => { let scope: string; beforeAll(() => { + originalEnvPort = process.env.PORT; scope = newProject({ packages: ['@nx/node', '@nx/express', '@nx/nest'], }); }); - afterAll(() => cleanupProject()); + 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 () => { @@ -445,7 +487,10 @@ ${jslib}(); it('should remove previous output before building with the --deleteOutputPath option set', async () => { const appName = uniq('app'); - runCLI(`generate @nx/node:app ${appName} --no-interactive`); + const port = getRandomPort(); + process.env.PORT = `${port}`; + + runCLI(`generate @nx/node:app ${appName} --port=${port} --no-interactive`); // deleteOutputPath should default to true createFile(`dist/apps/${appName}/_should_remove.txt`); @@ -481,8 +526,11 @@ ${jslib}(); const appName = uniq('app1'); const libName = uniq('@my-org/lib1'); + const port = getRandomPort(); + process.env.PORT = `${port}`; + runCLI( - `generate @nx/node:app ${appName} --project-name-and-root-format=as-provided --no-interactive` + `generate @nx/node:app ${appName} --project-name-and-root-format=as-provided --port=${port} --no-interactive` ); // check files are generated without the layout directory ("apps/") and From 717d45f884984ebbb5c3692dca2bbc58043b9478 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leosvel=20P=C3=A9rez=20Espinosa?= Date: Mon, 5 Feb 2024 18:49:53 +0100 Subject: [PATCH 12/26] fix(misc): handle workspaces if no plugin selected in nx init and only generate files after prompts (#21606) --- .../command-line/init/implementation/utils.ts | 13 +++++++++- packages/nx/src/command-line/init/init-v1.ts | 12 +-------- packages/nx/src/command-line/init/init-v2.ts | 25 +++++++++++-------- 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/packages/nx/src/command-line/init/implementation/utils.ts b/packages/nx/src/command-line/init/implementation/utils.ts index 4f51fb17f0f9f..40f624709fc34 100644 --- a/packages/nx/src/command-line/init/implementation/utils.ts +++ b/packages/nx/src/command-line/init/implementation/utils.ts @@ -16,7 +16,7 @@ import { } from '../../../utils/package-manager'; import { joinPathFragments } from '../../../utils/path'; import { nxVersion } from '../../../utils/versions'; -import { readFileSync, writeFileSync } from 'fs'; +import { existsSync, readFileSync, writeFileSync } from 'fs'; export function createNxJsonFile( repoRoot: string, @@ -223,3 +223,14 @@ export function printFinalMessage({ ].filter(Boolean), }); } + +export function isMonorepo(packageJson: PackageJson) { + if (!!packageJson.workspaces) return true; + + if (existsSync('pnpm-workspace.yaml') || existsSync('pnpm-workspace.yml')) + return true; + + if (existsSync('lerna.json')) return true; + + return false; +} diff --git a/packages/nx/src/command-line/init/init-v1.ts b/packages/nx/src/command-line/init/init-v1.ts index 4ffac5738d9d4..468f2207dc3a0 100644 --- a/packages/nx/src/command-line/init/init-v1.ts +++ b/packages/nx/src/command-line/init/init-v1.ts @@ -12,6 +12,7 @@ import { runNxSync } from '../../utils/child-process'; import { directoryExists, readJsonFile } from '../../utils/fileutils'; import { PackageJson } from '../../utils/package-json'; import { nxVersion } from '../../utils/versions'; +import { isMonorepo } from './implementation/utils'; export interface InitArgs { addE2e: boolean; @@ -106,17 +107,6 @@ function isNestCLI(packageJson: PackageJson) { ); } -function isMonorepo(packageJson: PackageJson) { - if (!!packageJson.workspaces) return true; - - if (existsSync('pnpm-workspace.yaml') || existsSync('pnpm-workspace.yml')) - return true; - - if (existsSync('lerna.json')) return true; - - return false; -} - function setupDotNxInstallation(version: string) { if (process.platform !== 'win32') { console.log( diff --git a/packages/nx/src/command-line/init/init-v2.ts b/packages/nx/src/command-line/init/init-v2.ts index 011fddb2ea296..30f6a955e84e3 100644 --- a/packages/nx/src/command-line/init/init-v2.ts +++ b/packages/nx/src/command-line/init/init-v2.ts @@ -10,6 +10,7 @@ import { nxVersion } from '../../utils/versions'; import { addDepsToPackageJson, createNxJsonFile, + isMonorepo, runInstall, updateGitIgnore, } from './implementation/utils'; @@ -19,6 +20,7 @@ import { addNxToAngularCliRepo } from './implementation/angular'; import { globWithWorkspaceContext } from '../../utils/workspace-context'; import { connectExistingRepoToNxCloudPrompt } from '../connect/connect-to-nx-cloud'; import { addNxToNpmRepo } from './implementation/add-nx-to-npm-repo'; +import { addNxToMonorepo } from './implementation/add-nx-to-monorepo'; export interface InitArgs { interactive: boolean; @@ -59,22 +61,17 @@ export async function initHandler(options: InitArgs): Promise { return; } - const repoRoot = process.cwd(); - const cacheableOperations: string[] = []; - createNxJsonFile(repoRoot, [], cacheableOperations, {}); - - const pmc = getPackageManagerCommand(); - - updateGitIgnore(repoRoot); - const detectPluginsResponse = await detectPlugins(); if (!detectPluginsResponse?.plugins.length) { // If no plugins are detected/chosen, guide users to setup // their targetDefaults correctly so their package scripts will work. - await addNxToNpmRepo({ - interactive: options.interactive, - }); + const packageJson: PackageJson = readJsonFile('package.json'); + if (isMonorepo(packageJson)) { + await addNxToMonorepo({ interactive: options.interactive }); + } else { + await addNxToNpmRepo({ interactive: options.interactive }); + } } else { const useNxCloud = options.nxCloud ?? @@ -82,6 +79,12 @@ export async function initHandler(options: InitArgs): Promise { ? await connectExistingRepoToNxCloudPrompt() : false); + const repoRoot = process.cwd(); + const pmc = getPackageManagerCommand(); + + createNxJsonFile(repoRoot, [], [], {}); + updateGitIgnore(repoRoot); + addDepsToPackageJson(repoRoot, detectPluginsResponse?.plugins ?? []); output.log({ title: '📦 Installing Nx' }); From 19fc320e201cee307750177532d264fed6fd1334 Mon Sep 17 00:00:00 2001 From: Isaac Mann Date: Mon, 5 Feb 2024 12:55:07 -0500 Subject: [PATCH 13/26] docs(core): redirect for nx-agents (#21617) --- nx-dev/nx-dev/redirect-rules.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nx-dev/nx-dev/redirect-rules.js b/nx-dev/nx-dev/redirect-rules.js index a30f9e3ba567d..ca124401d5c33 100644 --- a/nx-dev/nx-dev/redirect-rules.js +++ b/nx-dev/nx-dev/redirect-rules.js @@ -452,7 +452,9 @@ const nxCloudUrls = { '/nx-cloud/concepts/scenarios': '/ci/concepts/cache-security', '/nx-cloud/account/encryption': '/ci/recipes/security/encryption', '/nx-cloud/concepts/encryption': '/ci/recipes/security/encryption', - '/nx-cloud/features/nx-cloud-workflows': '/ci/features/nx-agents', + '/nx-cloud/features/nx-cloud-workflows': + '/ci/features/distribute-task-execution', + '/ci/features/nx-agents': '/ci/features/distribute-task-execution', '/ci': '/ci/intro/ci-with-nx', '/nx-cloud/:path*': '/ci/:path*', }; From 7513da3aca4cc5a2f9fb3c8bb7f497a000180211 Mon Sep 17 00:00:00 2001 From: Katerina Skroumpelou Date: Mon, 5 Feb 2024 19:58:50 +0200 Subject: [PATCH 14/26] fix(nx-dev): redirect core-features page (#21616) --- nx-dev/nx-dev/redirect-rules.js | 1 + 1 file changed, 1 insertion(+) diff --git a/nx-dev/nx-dev/redirect-rules.js b/nx-dev/nx-dev/redirect-rules.js index ca124401d5c33..97d75821a9e38 100644 --- a/nx-dev/nx-dev/redirect-rules.js +++ b/nx-dev/nx-dev/redirect-rules.js @@ -457,6 +457,7 @@ const nxCloudUrls = { '/ci/features/nx-agents': '/ci/features/distribute-task-execution', '/ci': '/ci/intro/ci-with-nx', '/nx-cloud/:path*': '/ci/:path*', + '/core-features/:path*': '/features/:path*', }; /** From d83300a285860a4376814294cf5b461aa798c55a Mon Sep 17 00:00:00 2001 From: Isaac Mann Date: Mon, 5 Feb 2024 13:18:58 -0500 Subject: [PATCH 15/26] docs(core): update nx node matrix (#21608) --- .../documents/nx-nodejs-typescript-version-matrix.md | 5 +++-- docs/shared/packages/workspace/nx-compatibility-matrix.md | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/generated/packages/workspace/documents/nx-nodejs-typescript-version-matrix.md b/docs/generated/packages/workspace/documents/nx-nodejs-typescript-version-matrix.md index 9df66470f6762..01fd5acc5e993 100644 --- a/docs/generated/packages/workspace/documents/nx-nodejs-typescript-version-matrix.md +++ b/docs/generated/packages/workspace/documents/nx-nodejs-typescript-version-matrix.md @@ -11,5 +11,6 @@ and the version of NodeJS that we tested it against. | 13.x | 10.x, 12.x, 14.x | ~4.6.2 | | 14.x | 12.x, 14.x, 16.x | ~4.7.2 | | 15.x | 14.x, 16.x, 18.x | ~5.0.0 | -| 16.x (previous) | 16.x, 18.x, 20.x | ~5.1.0 | -| 17.x (latest) | 18.x, 20.x | ~5.1.0 | +| 16.x | 16.x, 18.x, 20.x | ~5.1.0 | +| 17.x (previous) | 18.x, 20.x | ~5.1.0 | +| 18.x (latest) | 18.x, 20.x | ~5.1.0 | diff --git a/docs/shared/packages/workspace/nx-compatibility-matrix.md b/docs/shared/packages/workspace/nx-compatibility-matrix.md index 9df66470f6762..01fd5acc5e993 100644 --- a/docs/shared/packages/workspace/nx-compatibility-matrix.md +++ b/docs/shared/packages/workspace/nx-compatibility-matrix.md @@ -11,5 +11,6 @@ and the version of NodeJS that we tested it against. | 13.x | 10.x, 12.x, 14.x | ~4.6.2 | | 14.x | 12.x, 14.x, 16.x | ~4.7.2 | | 15.x | 14.x, 16.x, 18.x | ~5.0.0 | -| 16.x (previous) | 16.x, 18.x, 20.x | ~5.1.0 | -| 17.x (latest) | 18.x, 20.x | ~5.1.0 | +| 16.x | 16.x, 18.x, 20.x | ~5.1.0 | +| 17.x (previous) | 18.x, 20.x | ~5.1.0 | +| 18.x (latest) | 18.x, 20.x | ~5.1.0 | From bca5b70553e3c5c9f12e2f8dfec72b333d2c8b30 Mon Sep 17 00:00:00 2001 From: Jack Hsu Date: Mon, 5 Feb 2024 13:27:26 -0500 Subject: [PATCH 16/26] chore(misc): re-enable e2e-react-core tests (#21542) --- e2e/react-core/src/react.test.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/e2e/react-core/src/react.test.ts b/e2e/react-core/src/react.test.ts index fa9bf06ee2240..5e463af4b72c1 100644 --- a/e2e/react-core/src/react.test.ts +++ b/e2e/react-core/src/react.test.ts @@ -125,7 +125,7 @@ describe('React Applications', () => { }, 500000); // TODO(crystal, @jaysoo): Investigate why this is failing. - xit('should be able to use Vite to build and test apps', async () => { + it('should be able to use Vite to build and test apps', async () => { const appName = uniq('app'); const libName = uniq('lib'); @@ -153,7 +153,11 @@ describe('React Applications', () => { checkFilesExist(`dist/apps/${appName}/index.html`); if (runE2ETests()) { - const e2eResults = runCLI(`e2e ${appName}-e2e`); + const e2eResults = runCLI(`e2e ${appName}-e2e`, { + env: { + DEBUG: 'cypress:server:*', + }, + }); expect(e2eResults).toContain('All specs passed!'); expect(await killPorts()).toBeTruthy(); } From 0d9997a213f151c40d2553c55c1e9800ce6ab04f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leosvel=20P=C3=A9rez=20Espinosa?= Date: Mon, 5 Feb 2024 19:30:54 +0100 Subject: [PATCH 17/26] cleanup(testing): re-enable cypress e2e tests (#21604) --- e2e/cypress/src/cypress-legacy.test.ts | 3 +-- e2e/cypress/src/cypress.test.ts | 4 ++-- e2e/utils/process-utils.ts | 5 +++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/e2e/cypress/src/cypress-legacy.test.ts b/e2e/cypress/src/cypress-legacy.test.ts index 9977bc9c8297d..b927b2933a283 100644 --- a/e2e/cypress/src/cypress-legacy.test.ts +++ b/e2e/cypress/src/cypress-legacy.test.ts @@ -9,8 +9,7 @@ import { const TEN_MINS_MS = 600_000; -// TODO(crystal, @leosvelperez): Still need to investigate why this is failing on CI -xdescribe('Cypress E2E Test runner (legacy)', () => { +describe('Cypress E2E Test runner (legacy)', () => { beforeAll(() => { newProject({ packages: ['@nx/angular', '@nx/react'] }); }); diff --git a/e2e/cypress/src/cypress.test.ts b/e2e/cypress/src/cypress.test.ts index 5a07e8b848eef..a244d7146c985 100644 --- a/e2e/cypress/src/cypress.test.ts +++ b/e2e/cypress/src/cypress.test.ts @@ -12,8 +12,8 @@ import { } from '@nx/e2e/utils'; const TEN_MINS_MS = 600_000; -// TODO(crystal, @leosvelperez): Still need to investigate why this is failing on CI -xdescribe('Cypress E2E Test runner', () => { + +describe('Cypress E2E Test runner', () => { const myapp = uniq('myapp'); beforeAll(() => { diff --git a/e2e/utils/process-utils.ts b/e2e/utils/process-utils.ts index e17997c0a5173..e3dfea00f7d90 100644 --- a/e2e/utils/process-utils.ts +++ b/e2e/utils/process-utils.ts @@ -13,14 +13,15 @@ export const promisifiedTreeKill: ( export async function killPort(port: number): Promise { if (await portCheck(port)) { + let killPortResult; try { logInfo(`Attempting to close port ${port}`); - await kill(port); + killPortResult = await kill(port); await new Promise((resolve) => setTimeout(() => resolve(), KILL_PORT_DELAY) ); if (await portCheck(port)) { - logError(`Port ${port} still open`); + logError(`Port ${port} still open`, JSON.stringify(killPortResult)); } else { logSuccess(`Port ${port} successfully closed`); return true; From 86ba07e2a8f2c642ddea096636677e073267dc18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leosvel=20P=C3=A9rez=20Espinosa?= Date: Mon, 5 Feb 2024 20:06:58 +0100 Subject: [PATCH 18/26] cleanup(angular): re-add assertion to expect successful port kill in e2e test (#21620) --- e2e/angular-core/src/projects.test.ts | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/e2e/angular-core/src/projects.test.ts b/e2e/angular-core/src/projects.test.ts index 5e9435ba2fcb7..5f3173c6bdec6 100644 --- a/e2e/angular-core/src/projects.test.ts +++ b/e2e/angular-core/src/projects.test.ts @@ -126,22 +126,13 @@ describe('Angular Projects', () => { ); // check e2e tests - let appPort = 4958; - updateJson(join(app1, 'project.json'), (config) => { - config.targets.serve.options ??= {}; - config.targets.serve.options.port = appPort; - return config; - }); if (runE2ETests()) { - const e2eResults = runCLI( - `e2e ${app1}-e2e --config baseUrl=http://localhost:${appPort}` - ); + const e2eResults = runCLI(`e2e ${app1}-e2e`); expect(e2eResults).toContain('All specs passed!'); - // TODO(leo): check why the port is not being killed and add assertion after fixing it - await killPort(appPort); + expect(await killPort(4200)).toBeTruthy(); } - appPort = 4207; + const appPort = 4207; const process = await runCommandUntil( `serve ${app1} -- --port=${appPort}`, (output) => output.includes(`listening on localhost:${appPort}`) From 758c1ab70ef9b218824210cc6f1f2871ffd0a6a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leosvel=20P=C3=A9rez=20Espinosa?= Date: Mon, 5 Feb 2024 20:08:38 +0100 Subject: [PATCH 19/26] fix(angular): support inferred cypress targets in setup-mf generator (#21619) --- .../src/module-federation.test.ts | 32 +++++++------------ .../setup-mf/lib/add-cypress-workaround.ts | 7 ++-- 2 files changed, 17 insertions(+), 22 deletions(-) diff --git a/e2e/angular-module-federation/src/module-federation.test.ts b/e2e/angular-module-federation/src/module-federation.test.ts index 6ff5ab7881be8..642b71382742d 100644 --- a/e2e/angular-module-federation/src/module-federation.test.ts +++ b/e2e/angular-module-federation/src/module-federation.test.ts @@ -371,21 +371,17 @@ describe('Angular Module Federation', () => { ); // Build host and remote - const buildOutput = await runCommandUntil(`build ${host}`, (output) => - output.includes('Successfully ran target build') - ); - await killProcessAndPorts(buildOutput.pid); - const remoteOutput = await runCommandUntil(`build ${remote}`, (output) => - output.includes('Successfully ran target build') - ); - await killProcessAndPorts(remoteOutput.pid); + const buildHostOutput = runCLI(`build ${host}`); + expect(buildHostOutput).toContain('Successfully ran target build'); + const buildRemoteOutput = runCLI(`build ${remote}`); + expect(buildRemoteOutput).toContain('Successfully ran target build'); if (runE2ETests()) { - const hostE2eResults = await runCommandUntil( + const e2eProcess = await runCommandUntil( `e2e ${host}-e2e --no-watch --verbose`, (output) => output.includes('All specs passed!') ); - await killProcessAndPorts(hostE2eResults.pid, hostPort, hostPort + 1); + await killProcessAndPorts(e2eProcess.pid, hostPort, hostPort + 1); } }, 500_000); @@ -467,21 +463,17 @@ describe('Angular Module Federation', () => { ); // Build host and remote - const buildOutput = await runCommandUntil(`build ${host}`, (output) => - output.includes('Successfully ran target build') - ); - await killProcessAndPorts(buildOutput.pid); - const remoteOutput = await runCommandUntil(`build ${remote}`, (output) => - output.includes('Successfully ran target build') - ); - await killProcessAndPorts(remoteOutput.pid); + const buildHostOutput = runCLI(`build ${host}`); + expect(buildHostOutput).toContain('Successfully ran target build'); + const buildRemoteOutput = runCLI(`build ${remote}`); + expect(buildRemoteOutput).toContain('Successfully ran target build'); if (runE2ETests()) { - const hostE2eResults = await runCommandUntil( + const e2eProcess = await runCommandUntil( `e2e ${host}-e2e --no-watch --verbose`, (output) => output.includes('All specs passed!') ); - await killProcessAndPorts(hostE2eResults.pid, hostPort, hostPort + 1); + await killProcessAndPorts(e2eProcess.pid, hostPort, hostPort + 1); } }, 500_000); }); diff --git a/packages/angular/src/generators/setup-mf/lib/add-cypress-workaround.ts b/packages/angular/src/generators/setup-mf/lib/add-cypress-workaround.ts index f3201fe91ea40..8d7b7560b5c76 100644 --- a/packages/angular/src/generators/setup-mf/lib/add-cypress-workaround.ts +++ b/packages/angular/src/generators/setup-mf/lib/add-cypress-workaround.ts @@ -2,8 +2,10 @@ // as Angular attempt to figure out how to fix HMR when styles.js // is attached to the index.html with type=module +import { CYPRESS_CONFIG_FILE_NAME_PATTERN } from '@nx/cypress/src/utils/config'; import type { ProjectConfiguration, Tree } from '@nx/devkit'; import { + glob, joinPathFragments, logger, readProjectConfiguration, @@ -31,8 +33,9 @@ export function addCypressOnErrorWorkaround(tree: Tree, schema: Schema) { } if ( - e2eProject.targets.e2e.executor !== '@nx/cypress:cypress' && - e2eProject.targets.e2e.executor !== '@nx/cypress:cypress' + e2eProject.targets?.e2e?.executor !== '@nx/cypress:cypress' && + !glob(tree, [`${e2eProject.root}/${CYPRESS_CONFIG_FILE_NAME_PATTERN}`]) + .length ) { // Not a cypress e2e project, skip return; From f950f9b68df78598cd0dee966fc6d67c49e382d2 Mon Sep 17 00:00:00 2001 From: Jason Jean Date: Mon, 5 Feb 2024 14:08:55 -0500 Subject: [PATCH 20/26] docs(core): document NX_ADD_PLUGINS environment variable (#21565) --- docs/shared/reference/environment-variables.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/shared/reference/environment-variables.md b/docs/shared/reference/environment-variables.md index a73f611aef2b8..e93e50d81470c 100644 --- a/docs/shared/reference/environment-variables.md +++ b/docs/shared/reference/environment-variables.md @@ -4,6 +4,7 @@ The following environment variables are ones that you can set to change the beha | Property | Type | Description | | -------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| NX_ADD_PLUGINS | boolean | If set to false, Nx will not add plugins to infer tasks. This is true by default. Workspaces created before Nx 18 will have this disabled via a migration for backwards compatibility | | NX_BASE | string | The default base branch to use when calculating the affected projects. Can be overridden on the command line with `--base`. | | NX_CACHE_DIRECTORY | string | The cache for task outputs is stored in `node_modules/.cache/nx` by default. Set this variable to use a different directory. | | NX_CACHE_PROJECT_GRAPH | boolean | If set to `false`, disables the project graph cache. Most useful when developing a plugin that modifies the project graph. | From 55d3055d0e1b835e0709b0600036a78a35b2cd77 Mon Sep 17 00:00:00 2001 From: Altan Stalker Date: Mon, 5 Feb 2024 14:32:08 -0500 Subject: [PATCH 21/26] docs(nx-cloud): add troubleshooting section for "ci execution failed" (#21622) --- docs/generated/manifests/ci.json | 57 +++++++++++++++++ docs/generated/manifests/menus.json | 42 +++++++++++++ docs/map.json | 12 ++++ docs/nx-cloud/reference/nx-cloud-cli.md | 2 +- .../troubleshooting/ci-execution-failed.md | 62 +++++++++++++++++++ docs/shared/reference/sitemap.md | 2 + 6 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 docs/nx-cloud/troubleshooting/ci-execution-failed.md diff --git a/docs/generated/manifests/ci.json b/docs/generated/manifests/ci.json index fbbe669ccb76d..bc2539e604cae 100644 --- a/docs/generated/manifests/ci.json +++ b/docs/generated/manifests/ci.json @@ -675,6 +675,29 @@ "path": "/ci/recipes/on-premise", "tags": [] }, + { + "id": "troubleshooting", + "name": "Troubleshooting", + "description": "Learn how to solve common issues in Nx Cloud.", + "mediaImage": "", + "file": "", + "itemList": [ + { + "id": "ci-execution-failed", + "name": "CI Execution Failed", + "description": "", + "mediaImage": "", + "file": "nx-cloud/troubleshooting/ci-execution-failed", + "itemList": [], + "isExternal": false, + "path": "/ci/recipes/troubleshooting/ci-execution-failed", + "tags": [] + } + ], + "isExternal": false, + "path": "/ci/recipes/troubleshooting", + "tags": [] + }, { "id": "other", "name": "Other", @@ -1224,6 +1247,40 @@ "path": "/ci/recipes/on-premise/advanced-config", "tags": [] }, + "/ci/recipes/troubleshooting": { + "id": "troubleshooting", + "name": "Troubleshooting", + "description": "Learn how to solve common issues in Nx Cloud.", + "mediaImage": "", + "file": "", + "itemList": [ + { + "id": "ci-execution-failed", + "name": "CI Execution Failed", + "description": "", + "mediaImage": "", + "file": "nx-cloud/troubleshooting/ci-execution-failed", + "itemList": [], + "isExternal": false, + "path": "/ci/recipes/troubleshooting/ci-execution-failed", + "tags": [] + } + ], + "isExternal": false, + "path": "/ci/recipes/troubleshooting", + "tags": [] + }, + "/ci/recipes/troubleshooting/ci-execution-failed": { + "id": "ci-execution-failed", + "name": "CI Execution Failed", + "description": "", + "mediaImage": "", + "file": "nx-cloud/troubleshooting/ci-execution-failed", + "itemList": [], + "isExternal": false, + "path": "/ci/recipes/troubleshooting/ci-execution-failed", + "tags": [] + }, "/ci/recipes/other": { "id": "other", "name": "Other", diff --git a/docs/generated/manifests/menus.json b/docs/generated/manifests/menus.json index 2d534e0a23173..657b493379468 100644 --- a/docs/generated/manifests/menus.json +++ b/docs/generated/manifests/menus.json @@ -6144,6 +6144,23 @@ ], "disableCollapsible": false }, + { + "name": "Troubleshooting", + "path": "/ci/recipes/troubleshooting", + "id": "troubleshooting", + "isExternal": false, + "children": [ + { + "name": "CI Execution Failed", + "path": "/ci/recipes/troubleshooting/ci-execution-failed", + "id": "ci-execution-failed", + "isExternal": false, + "children": [], + "disableCollapsible": false + } + ], + "disableCollapsible": false + }, { "name": "Other", "path": "/ci/recipes/other", @@ -6544,6 +6561,31 @@ "children": [], "disableCollapsible": false }, + { + "name": "Troubleshooting", + "path": "/ci/recipes/troubleshooting", + "id": "troubleshooting", + "isExternal": false, + "children": [ + { + "name": "CI Execution Failed", + "path": "/ci/recipes/troubleshooting/ci-execution-failed", + "id": "ci-execution-failed", + "isExternal": false, + "children": [], + "disableCollapsible": false + } + ], + "disableCollapsible": false + }, + { + "name": "CI Execution Failed", + "path": "/ci/recipes/troubleshooting/ci-execution-failed", + "id": "ci-execution-failed", + "isExternal": false, + "children": [], + "disableCollapsible": false + }, { "name": "Other", "path": "/ci/recipes/other", diff --git a/docs/map.json b/docs/map.json index 73cc8eeb3b048..5007597a49a96 100644 --- a/docs/map.json +++ b/docs/map.json @@ -1899,6 +1899,18 @@ } ] }, + { + "name": "Troubleshooting", + "id": "troubleshooting", + "description": "Learn how to solve common issues in Nx Cloud.", + "itemList": [ + { + "name": "CI Execution Failed", + "id": "ci-execution-failed", + "file": "nx-cloud/troubleshooting/ci-execution-failed" + } + ] + }, { "name": "Other", "id": "other", diff --git a/docs/nx-cloud/reference/nx-cloud-cli.md b/docs/nx-cloud/reference/nx-cloud-cli.md index 0770d9b533ff8..bc9d38445d0ee 100644 --- a/docs/nx-cloud/reference/nx-cloud-cli.md +++ b/docs/nx-cloud/reference/nx-cloud-cli.md @@ -86,7 +86,7 @@ individual commands as follows: ## npx nx-cloud stop-all-agents -Same as `npx nx-cloud complete-ci-run` and `npx nx-cloud complete-run-group`. +Same as `npx nx-cloud complete-ci-run`. This command tells Nx Cloud to terminate all agents associated with this CI pipeline execution. Invoking this command is not needed anymore. New versions of Nx Cloud can track when the main job terminates diff --git a/docs/nx-cloud/troubleshooting/ci-execution-failed.md b/docs/nx-cloud/troubleshooting/ci-execution-failed.md new file mode 100644 index 0000000000000..179d29c1eeffc --- /dev/null +++ b/docs/nx-cloud/troubleshooting/ci-execution-failed.md @@ -0,0 +1,62 @@ +# Understanding 'CI Execution Failed' + +## Task Runner-Related + +### No additional tasks detected + +Nx Cloud is not aware of any more tasks to distribute. This can occur if Nx Cloud thinks it is done receiving tasks to distribute and all existing tasks have been completed. + +If you are receiving this error before your full pipeline has completed, consider using [--stop-agents-after](https://nx.dev/ci/reference/nx-cloud-cli#stopagentsafter) with the target set to the last target run in your pipeline. + +### The Nx Cloud heartbeat process failed to report its status in time + +While running in CI environments, Nx Cloud spawns a background process called the "heartbeat" to help maintain status synchronization between itself and external platforms. When the heartbeat process does not report to Nx Cloud for 30 seconds or longer, Nx Cloud assumes something has gone wrong and terminates the current CI Pipeline Execution. + +This behavior can be disabled by setting the [--require-explicit-completion](https://nx.dev/ci/reference/nx-cloud-cli#requireexplicitcompletion) flag to `true` on your `nx-cloud start-ci-run` command. + +### A command was issued to stop all Nx Cloud agents + +Nx Cloud provides two commands to forcibly stop agents, [stop-all-agents and complete-ci-run](https://nx.dev/ci/reference/nx-cloud-cli#npx-nxcloud-stopallagents). + +Once these commands are invoked, the current CI Pipeline Execution is closed and can no longer receive new work. + +### Nx Cloud agents were stopped due to an error + +Nx Cloud detected a failed task in the current CI Pipeline Execution and has halted further execution. + +This behavior can be disabled by setting the [--stop-agents-on-failure](https://nx.dev/ci/reference/nx-cloud-cli#stopagentsonfailure) flag to `false` on your `nx-cloud start-ci-run` command. + +## Nx Agents-Related + +### Failed to start Nx Agents workflow + +Nx Cloud was unable to start the agents workflow with the configuration provided to `nx-cloud start-ci-run`. View the CI Pipeline Execution in the Nx Cloud UI for additional details. + +### Unable to get workflow status from Nx Agents + +Nx Cloud was unable to communicate with the Nx Agents assigned to a workflow for the current CI Pipeline Execution. View the CI Pipeline Execution in the Nx Cloud UI for additional details. + +## Status Reconciliation-Related + +### One or more workflows were cancelled + +The current CI Pipeline Execution had a workflow cancelled due to either: + +- a manual request in the Nx Cloud UI, or +- a push to the same branch that already had a running workflow. + +### One or more workflows encountered a critical error + +The current CI Pipeline Execution encountered a critical error in a child execution environment. View the CI Pipeline Execution in the Nx Cloud UI for additional details. + +### One or more workflows failed + +The current CI Pipeline Execution had at least one workflow with failed steps. + +### One or more workflows encountered an error + +The current CI Pipeline Execution had at least one workflow that executed tasks which failed. See also: [Nx Cloud agents were stopped due to an error](#nx-cloud-agents-were-stopped-due-to-an-error) + +### One or more workflows timed out + +The current CI Pipeline Execution had at least one workflow that exceeded the timeout duration. View the CI Pipeline Execution in the Nx Cloud UI for additional details. diff --git a/docs/shared/reference/sitemap.md b/docs/shared/reference/sitemap.md index 341477c86a5be..f4b7f900f2f35 100644 --- a/docs/shared/reference/sitemap.md +++ b/docs/shared/reference/sitemap.md @@ -305,6 +305,8 @@ - [Authenticate via SAML](/ci/recipes/on-premise/auth-saml) - [Authenticate via SAML on Managed Version](/ci/recipes/on-premise/auth-saml-managed) - [Advanced Configuration](/ci/recipes/on-premise/advanced-config) + - [Troubleshooting](/ci/recipes/troubleshooting) + - [CI Execution Failed](/ci/recipes/troubleshooting/ci-execution-failed) - [Other](/ci/recipes/other) - [Record Non-Nx Commands](/ci/recipes/other/record-commands) - [Prepare applications for deployment via CI](/ci/recipes/other/ci-deployment) From 7086f5700e26294891a551f571853fb03b162acb Mon Sep 17 00:00:00 2001 From: Jack Hsu Date: Mon, 5 Feb 2024 14:33:42 -0500 Subject: [PATCH 22/26] fix(core): remove logic to reload process with esm loader for Node 18 (#21623) --- packages/nx/bin/init-local.ts | 54 ----------------------------------- 1 file changed, 54 deletions(-) diff --git a/packages/nx/bin/init-local.ts b/packages/nx/bin/init-local.ts index 94930c59d5cdf..34c96fc3ed690 100644 --- a/packages/nx/bin/init-local.ts +++ b/packages/nx/bin/init-local.ts @@ -13,27 +13,6 @@ import * as Mod from 'module'; */ export function initLocal(workspace: WorkspaceTypeAndRoot) { - // If module.register is not available, we need to restart the process with the experimental ESM loader. - // Otherwise, usage of `registerTsProject` will not work for `.ts` files using ESM. - // TODO: Remove this once Node 18 is out of LTS (March 2024). - if (shouldRestartWithExperimentalTsEsmLoader()) { - const child = require('child_process').fork( - require.resolve('./nx'), - process.argv.slice(2), - { - env: { - ...process.env, - RESTARTED_WITH_EXPERIMENTAL_TS_ESM_LOADER: '1', - }, - execArgv: execArgvWithExperimentalLoaderOptions(), - } - ); - child.on('close', (code: number | null) => { - if (code !== 0 && code !== null) process.exit(code); - }); - return; - } - process.env.NX_CLI_SET = 'true'; try { @@ -215,36 +194,3 @@ function monkeyPatchRequire() { // do some side-effect of your own }; } - -function shouldRestartWithExperimentalTsEsmLoader(): boolean { - // Already restarted with experimental loader - if (process.env.RESTARTED_WITH_EXPERIMENTAL_TS_ESM_LOADER === '1') - return false; - const nodeVersion = parseInt(process.versions.node.split('.')[0]); - // `--experimental-loader` is only supported in Nodejs >= 16 so there is no point restarting for older versions - if (nodeVersion < 16) return false; - // Node 20.6.0 adds `module.register`, otherwise we need to restart process with "--experimental-loader ts-node/esm". - return ( - !require('node:module').register && - moduleResolves('ts-node/esm') && - moduleResolves('typescript') - ); -} - -function execArgvWithExperimentalLoaderOptions() { - return [ - ...process.execArgv, - '--no-warnings', - '--experimental-loader', - require.resolve('ts-node/esm'), - ]; -} - -function moduleResolves(packageName: string) { - try { - require.resolve(packageName); - return true; - } catch { - return false; - } -} From fb0df72de58860e62717dd71331f6b1d108e9948 Mon Sep 17 00:00:00 2001 From: Philip Fulcher Date: Mon, 5 Feb 2024 12:53:55 -0700 Subject: [PATCH 23/26] docs(nx-dev): improvements to launch nx page (#21621) --- nx-dev/ui-conference/src/lib/launch-week/agenda.tsx | 2 +- nx-dev/ui-conference/src/lib/launch-week/announcements.tsx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/nx-dev/ui-conference/src/lib/launch-week/agenda.tsx b/nx-dev/ui-conference/src/lib/launch-week/agenda.tsx index 11ad325136d9a..8fa798652dee2 100644 --- a/nx-dev/ui-conference/src/lib/launch-week/agenda.tsx +++ b/nx-dev/ui-conference/src/lib/launch-week/agenda.tsx @@ -45,7 +45,7 @@ export function LaunchWeekAgenda(): JSX.Element { { type: 'event', time: '3:35pm', - title: 'Nx Release', + title: 'Releasing Nx Release', description: ``, speakers: ['James Henry'], videoUrl: '', diff --git a/nx-dev/ui-conference/src/lib/launch-week/announcements.tsx b/nx-dev/ui-conference/src/lib/launch-week/announcements.tsx index 1cbc82a5e932b..c0c3a3df60c38 100644 --- a/nx-dev/ui-conference/src/lib/launch-week/announcements.tsx +++ b/nx-dev/ui-conference/src/lib/launch-week/announcements.tsx @@ -17,7 +17,7 @@ export function LaunchWeekAnnouncements(): JSX.Element { {/* MONDAY */}
-
+
@@ -41,7 +41,7 @@ export function LaunchWeekAnnouncements(): JSX.Element { This is what Nx Project Crystal is all about.

-
+