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
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"
}
]
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/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/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/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/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.
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 = {
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 |
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. |
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)
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}`)
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/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/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;
});
});
diff --git a/e2e/next-core/src/next-appdir.test.ts b/e2e/next-core/src/next-appdir.test.ts
index a57d15ddb0cf5..5a0dcc7e9319c 100644
--- a/e2e/next-core/src/next-appdir.test.ts
+++ b/e2e/next-core/src/next-appdir.test.ts
@@ -1,12 +1,12 @@
import {
cleanupProject,
- isNotWindows,
+ killPorts,
newProject,
runCLI,
+ runE2ETests,
uniq,
updateFile,
} from '@nx/e2e/utils';
-import { checkApp } from './utils';
describe('Next.js App Router', () => {
let proj: string;
@@ -20,8 +20,7 @@ describe('Next.js App Router', () => {
afterAll(() => cleanupProject());
- // TODO: enable this when tests are passing again
- xit('should be able to generate and build app with default App Router', async () => {
+ it('should be able to generate and build app with default App Router', async () => {
const appName = uniq('app');
const jsLib = uniq('tslib');
@@ -31,7 +30,7 @@ describe('Next.js App Router', () => {
runCLI(`generate @nx/js:lib ${jsLib} --no-interactive`);
updateFile(
- `apps/${appName}/app/page.tsx`,
+ `apps/${appName}/src/app/page.tsx`,
`
import React from 'react';
import { ${jsLib} } from '@${proj}/${jsLib}';
@@ -40,7 +39,7 @@ describe('Next.js App Router', () => {
return (
{${jsLib}()}
);
- };
+ }
`
);
@@ -58,11 +57,15 @@ describe('Next.js App Router', () => {
`
);
- await checkApp(appName, {
- checkUnitTest: false,
- checkLint: true,
- checkE2E: isNotWindows(),
- checkExport: false,
- });
+ const lintResults = runCLI(`lint ${appName}`);
+ expect(lintResults).toContain('Successfully ran target lint');
+
+ if (runE2ETests()) {
+ const e2eResults = runCLI(
+ `e2e ${appName}-e2e --configuration=production`
+ );
+ expect(e2eResults).toContain('Successfully ran target e2e for project');
+ expect(await killPorts()).toBeTruthy();
+ }
}, 300_000);
});
diff --git a/e2e/next-core/src/next-legacy.test.ts b/e2e/next-core/src/next-legacy.test.ts
index 47613c41dc5b7..2583e4480016d 100644
--- a/e2e/next-core/src/next-legacy.test.ts
+++ b/e2e/next-core/src/next-legacy.test.ts
@@ -22,16 +22,11 @@ import { mkdirSync, removeSync } from 'fs-extra';
import { join } from 'path';
import { checkApp } from './utils';
-// TODO(crystal, @ndcunningham): Investigate why these tests are failing
-xdescribe('@nx/next (legacy)', () => {
+describe('@nx/next (legacy)', () => {
let proj: string;
let originalEnv: string;
let packageManager;
- afterEach(() => {
- cleanupProject();
- });
-
beforeAll(() => {
proj = newProject({
packages: ['@nx/next'],
@@ -99,7 +94,8 @@ xdescribe('@nx/next (legacy)', () => {
});
}, 1_000_000);
- it('should produce a self-contained artifact in dist', async () => {
+ // Reenable this test once we fix Error: Cannot find module 'ajv/dist/compile/codegen'
+ xit('should produce a self-contained artifact in dist', async () => {
// Remove apps/libs folder and use packages.
// Allows us to test other integrated monorepo setup that had a regression.
// See: https://github.com/nrwl/nx/issues/16658
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`
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
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();
}
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;
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 0000000000000..c297847ce0752
Binary files /dev/null and b/nx-dev/nx-dev/public/images/launch-nx/proj-crystal-launch.jpg differ
diff --git a/nx-dev/nx-dev/redirect-rules.js b/nx-dev/nx-dev/redirect-rules.js
index a30f9e3ba567d..97d75821a9e38 100644
--- a/nx-dev/nx-dev/redirect-rules.js
+++ b/nx-dev/nx-dev/redirect-rules.js
@@ -452,9 +452,12 @@ 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*',
+ '/core-features/:path*': '/features/:path*',
};
/**
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 5bf924cb04f94..c0c3a3df60c38 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
+
+
+
+
+
+
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/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;
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};`
);
}
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.
diff --git a/packages/nx/bin/init-local.ts b/packages/nx/bin/init-local.ts
index 6a82c9bbaebd4..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',
- 'ts-node/esm',
- ];
-}
-
-function moduleResolves(packageName: string) {
- try {
- require.resolve(packageName);
- return true;
- } catch {
- return false;
- }
-}
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' });
diff --git a/packages/nx/src/plugins/target-defaults/target-defaults-plugin.spec.ts b/packages/nx/src/plugins/target-defaults/target-defaults-plugin.spec.ts
index a13cd539042a3..5dfd18ddf023b 100644
--- a/packages/nx/src/plugins/target-defaults/target-defaults-plugin.spec.ts
+++ b/packages/nx/src/plugins/target-defaults/target-defaults-plugin.spec.ts
@@ -2,7 +2,7 @@ import * as memfs from 'memfs';
import '../../../src/internal-testing-utils/mock-fs';
-import { TargetDefaultsPlugin } from './target-defaults-plugin';
+import { getTargetInfo, TargetDefaultsPlugin } from './target-defaults-plugin';
import { CreateNodesContext } from '../../utils/nx-plugin';
const {
createNodes: [, createNodesFn],
@@ -74,6 +74,7 @@ describe('target-defaults plugin', () => {
"dependsOn": [
"^build",
],
+ "executor": "nx:run-commands",
},
},
},
@@ -114,6 +115,10 @@ describe('target-defaults plugin', () => {
"targets": {
"test": {
"command": "jest",
+ "executor": "nx:run-script",
+ "options": {
+ "script": "test",
+ },
},
},
},
@@ -156,6 +161,10 @@ describe('target-defaults plugin', () => {
"targets": {
"test": {
"command": "jest",
+ "executor": "nx:run-script",
+ "options": {
+ "script": "test",
+ },
},
},
},
@@ -225,6 +234,10 @@ describe('target-defaults plugin', () => {
"targets": {
"test": {
"command": "jest",
+ "executor": "nx:run-script",
+ "options": {
+ "script": "test",
+ },
Symbol(ONLY_MODIFIES_EXISTING_TARGET): true,
},
},
@@ -274,11 +287,13 @@ describe('target-defaults plugin', () => {
".": {
"targets": {
"echo": {
+ "executor": "nx:run-commands",
"options": {
"cwd": "{projectRoot}",
},
},
"echo2": {
+ "executor": "nx:run-commands",
"options": {
"cwd": "{projectRoot}",
},
@@ -336,11 +351,13 @@ describe('target-defaults plugin', () => {
".": {
"targets": {
"echo": {
+ "executor": "nx:run-commands",
"options": {
"cwd": "{projectRoot}",
},
},
"echo2": {
+ "executor": "nx:run-commands",
"options": {
"cwd": "{projectRoot}",
},
@@ -358,4 +375,93 @@ describe('target-defaults plugin', () => {
`);
});
});
+
+ describe('get target info', () => {
+ it('should include command for single command', () => {
+ const result = getTargetInfo(
+ 'echo',
+ {
+ targets: {
+ echo: {
+ command: 'echo hi',
+ },
+ },
+ },
+ null
+ );
+ expect(result).toMatchInlineSnapshot(`
+ {
+ "command": "echo hi",
+ }
+ `);
+ });
+
+ it('should include command for run-commands', () => {
+ const result = getTargetInfo(
+ 'echo',
+ {
+ targets: {
+ echo: {
+ executor: 'nx:run-commands',
+ options: {
+ command: 'echo hi',
+ cwd: '{projectRoot}',
+ },
+ },
+ },
+ },
+ null
+ );
+ expect(result).toMatchInlineSnapshot(`
+ {
+ "executor": "nx:run-commands",
+ "options": {
+ "command": "echo hi",
+ },
+ }
+ `);
+ });
+
+ it('should include script for run-script', () => {
+ expect(
+ getTargetInfo('build', null, {
+ scripts: {
+ build: 'echo hi',
+ },
+ })
+ ).toMatchInlineSnapshot(`
+ {
+ "executor": "nx:run-script",
+ "options": {
+ "script": "build",
+ },
+ }
+ `);
+
+ expect(
+ getTargetInfo('echo', null, {
+ scripts: {
+ build: 'echo hi',
+ },
+ nx: {
+ targets: {
+ echo: {
+ executor: 'nx:run-script',
+ options: {
+ script: 'build',
+ },
+ },
+ },
+ },
+ })
+ ).toMatchInlineSnapshot(`
+ {
+ "executor": "nx:run-script",
+ "options": {
+ "script": "build",
+ },
+ }
+ `);
+ });
+ });
});
diff --git a/packages/nx/src/plugins/target-defaults/target-defaults-plugin.ts b/packages/nx/src/plugins/target-defaults/target-defaults-plugin.ts
index 72d16da42604d..884140531a244 100644
--- a/packages/nx/src/plugins/target-defaults/target-defaults-plugin.ts
+++ b/packages/nx/src/plugins/target-defaults/target-defaults-plugin.ts
@@ -88,9 +88,13 @@ export const TargetDefaultsPlugin: NxPluginV2 = {
// Prevents `build` from overwriting `@nx/js:tsc` if both are present
// and build is specified later in the ordering.
if (!modifiedTargets[targetName] || targetName !== defaultSpecifier) {
- modifiedTargets[targetName] = JSON.parse(
+ const defaults = JSON.parse(
JSON.stringify(targetDefaults[defaultSpecifier])
);
+ modifiedTargets[targetName] = {
+ ...getTargetInfo(targetName, projectJson, packageJson),
+ ...defaults,
+ };
}
// TODO: Remove this after we figure out a way to define new targets
// in target defaults
@@ -140,3 +144,93 @@ function readJsonOrNull(path: string) {
return null;
}
}
+
+/**
+ * This fn gets target info that would make a target uniquely compatible
+ * with what is described by project.json or package.json. As the merge process
+ * for config happens, without this, the target defaults may be compatible
+ * with a config from a plugin and then that combined target be incompatible
+ * with the project json configuration resulting in the target default values
+ * being scrapped. By adding enough information from the project.json / package.json,
+ * we can make sure that the target after merging is compatible with the defined target.
+ */
+export function getTargetInfo(
+ target: string,
+ projectJson: Pick,
+ packageJson: Pick
+) {
+ const projectJsonTarget = projectJson?.targets?.[target];
+ const packageJsonTarget = packageJson?.nx?.targets?.[target];
+
+ const executor = getTargetExecutor(target, projectJson, packageJson);
+ const targetOptions = {
+ ...packageJsonTarget?.options,
+ ...projectJsonTarget?.options,
+ };
+
+ if (projectJsonTarget?.command) {
+ return {
+ command: projectJsonTarget?.command,
+ };
+ }
+
+ if (executor === 'nx:run-commands') {
+ if (targetOptions?.command) {
+ return {
+ executor: 'nx:run-commands',
+ options: {
+ command: projectJsonTarget.options?.command,
+ },
+ };
+ } else if (targetOptions?.commands) {
+ return {
+ executor: 'nx:run-commands',
+ options: {
+ commands: targetOptions.commands,
+ },
+ };
+ }
+ return {
+ executor: 'nx:run-commands',
+ };
+ }
+
+ if (executor === 'nx:run-script') {
+ return {
+ executor: 'nx:run-script',
+ options: {
+ script: targetOptions?.script ?? target,
+ },
+ };
+ }
+
+ if (executor) {
+ return { executor };
+ }
+
+ return {};
+}
+
+function getTargetExecutor(
+ target: string,
+ projectJson: Pick,
+ packageJson: Pick
+) {
+ const projectJsonTarget = projectJson?.targets?.[target];
+ const packageJsonTarget = packageJson?.nx?.targets?.[target];
+ const packageJsonScript = packageJson?.scripts?.[target];
+
+ if (projectJsonTarget?.command) {
+ return 'nx:run-commands';
+ }
+
+ if (
+ !projectJsonTarget?.executor &&
+ !packageJsonTarget?.executor &&
+ packageJsonScript
+ ) {
+ return 'nx:run-script';
+ }
+
+ return projectJsonTarget?.executor ?? packageJsonTarget?.executor;
+}
diff --git a/packages/webpack/src/plugins/nx-webpack-plugin/lib/apply-base-config.ts b/packages/webpack/src/plugins/nx-webpack-plugin/lib/apply-base-config.ts
index 9d02cc12a8fa2..3ab32b13511a3 100644
--- a/packages/webpack/src/plugins/nx-webpack-plugin/lib/apply-base-config.ts
+++ b/packages/webpack/src/plugins/nx-webpack-plugin/lib/apply-base-config.ts
@@ -19,7 +19,6 @@ import { createLoaderFromCompiler } from './compiler-loaders';
import { NormalizedNxWebpackPluginOptions } from '../nx-webpack-plugin-options';
import TerserPlugin = require('terser-webpack-plugin');
import nodeExternals = require('webpack-node-externals');
-import ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const IGNORED_WEBPACK_WARNINGS = [
/The comment file/i,
@@ -223,6 +222,7 @@ function applyNxDependentConfig(
};
if (!options?.skipTypeChecking) {
+ const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
plugins.push(
new ForkTsCheckerWebpackPlugin({
typescript: {