Compare commits

...

26 Commits

Author SHA1 Message Date
Jack Hsu 7b0c86924f fix(webpack): require ForkTsCheckerWebpackPlugin only as required (#21629)
(cherry picked from commit bf62661bb9)
2024-02-05 18:20:03 -05:00
Nicholas Cunningham fde900b607 fix(nextjs): Enable next e2e test (#21625)
(cherry picked from commit 7285253b8e)
2024-02-05 18:20:00 -05:00
Craigory Coppola 2ebfee5781 fix(core): prevent target defaults from being discarded during merge process (#21624)
(cherry picked from commit b1253d8af6)
2024-02-05 18:19:57 -05:00
Philip Fulcher 7ee4137638 docs(nx-dev): improvements to launch nx page (#21621)
(cherry picked from commit fb0df72de5)
2024-02-05 18:19:53 -05:00
Jack Hsu 5a8a56dc15 fix(core): remove logic to reload process with esm loader for Node 18 (#21623)
(cherry picked from commit 7086f5700e)
2024-02-05 18:19:48 -05:00
Altan Stalker f7ed73d088 docs(nx-cloud): add troubleshooting section for "ci execution failed" (#21622)
(cherry picked from commit 55d3055d0e)
2024-02-05 18:19:42 -05:00
Jason Jean cb8e02bf88 docs(core): document NX_ADD_PLUGINS environment variable (#21565)
(cherry picked from commit f950f9b68d)
2024-02-05 18:19:39 -05:00
Leosvel Pérez Espinosa fbb24615b2 fix(angular): support inferred cypress targets in setup-mf generator (#21619)
(cherry picked from commit 758c1ab70e)
2024-02-05 18:19:36 -05:00
Leosvel Pérez Espinosa df446f7b1e cleanup(angular): re-add assertion to expect successful port kill in e2e test (#21620)
(cherry picked from commit 86ba07e2a8)
2024-02-05 18:19:33 -05:00
Leosvel Pérez Espinosa 2a7743c543 cleanup(testing): re-enable cypress e2e tests (#21604)
(cherry picked from commit 0d9997a213)
2024-02-05 18:19:30 -05:00
Jack Hsu 563ebc2494 chore(misc): re-enable e2e-react-core tests (#21542)
(cherry picked from commit bca5b70553)
2024-02-05 18:19:26 -05:00
Isaac Mann 31f61f8527 docs(core): update nx node matrix (#21608)
(cherry picked from commit d83300a285)
2024-02-05 18:19:23 -05:00
Katerina Skroumpelou 8764424e9b fix(nx-dev): redirect core-features page (#21616)
(cherry picked from commit 7513da3aca)
2024-02-05 18:19:19 -05:00
Isaac Mann 81d73e3512 docs(core): redirect for nx-agents (#21617)
(cherry picked from commit 19fc320e20)
2024-02-05 18:19:17 -05:00
Leosvel Pérez Espinosa 4056645034 fix(misc): handle workspaces if no plugin selected in nx init and only generate files after prompts (#21606)
(cherry picked from commit 717d45f884)
2024-02-05 18:19:13 -05:00
Nicholas Cunningham 66f3bcb633 fix(node): Broken E2E tests (#21569)
(cherry picked from commit 371a1ed6b8)
2024-02-05 18:19:10 -05:00
Rares Matei bbe8e6678d chore(repo): install lsof and other e2e deps (#21615)
(cherry picked from commit f38ad96ec5)
2024-02-05 18:19:07 -05:00
Jack Hsu c5a44ef4ae fix(core): pass the full resolved path of ts-node/esm when reloading the CLI (#21607)
(cherry picked from commit b97958c1b3)
2024-02-05 18:19:03 -05:00
Jack Hsu 8dd1db291e fix(nextjs): move next/constants from top-level import to when it is needed (#21612)
(cherry picked from commit 44d33c243a)
2024-02-05 18:19:00 -05:00
Isaac Mann 1fe809f6d6 docs(core): project crystal video (#21605)
(cherry picked from commit 140d22f7a3)
2024-02-05 18:18:56 -05:00
Philip Fulcher 489aa8f2e0 docs(nx-dev): add launch nx announcements template (#21557)
Co-authored-by: Juri <juri.strumpflohner@gmail.com>
(cherry picked from commit a6ddf08201)
2024-02-05 18:18:53 -05:00
Laurence "DC5B" Lord 7a44f6ea22 Correct typo (#21597)
Co-authored-by: Isaac Mann <isaacplmann@gmail.com>
(cherry picked from commit a02af1615a)
2024-02-05 18:18:50 -05:00
Leosvel Pérez Espinosa 577061c5a1 fix(angular): fix wrong trailing comma in mf bootstrap code generation (#21600)
(cherry picked from commit 04211785e8)
2024-02-05 18:18:46 -05:00
Pascal Brewing 984edacd41 docs(core): add analogjs nx plugin to approved-plugins.json (#21080)
Co-authored-by: PASCAL BREWING <pascal.brewing@mercedes-benz.com>
(cherry picked from commit 48296b043d)
2024-02-05 18:18:43 -05:00
Jack Hsu 7163769b39 chore(nextjs): remove tests that are unnecessary (#21566)
(cherry picked from commit a524665073)
2024-02-05 18:18:40 -05:00
Miroslav Jonaš 01831e3e67 chore(linter): fix and reenable disabled eslint test (#21601)
(cherry picked from commit c1ede83d62)
2024-02-05 18:18:37 -05:00
42 changed files with 665 additions and 216 deletions
+4 -1
View File
@@ -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
+5
View File
@@ -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"
}
]
+57
View File
@@ -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",
+42
View File
@@ -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",
@@ -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 = {
@@ -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 |
+12
View File
@@ -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",
+1 -1
View File
@@ -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
@@ -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.
+6 -1
View File
@@ -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.
+1 -1
View File
@@ -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 = {
@@ -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 |
@@ -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. |
+2
View File
@@ -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)
+3 -12
View File
@@ -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}`)
@@ -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);
});
+1 -2
View File
@@ -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'] });
});
+2 -2
View File
@@ -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(() => {
+27 -14
View File
@@ -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;
});
});
+15 -12
View File
@@ -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 (
<p>{${jsLib}()}</p>
);
};
}
`
);
@@ -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);
});
+3 -7
View File
@@ -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
-6
View File
@@ -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`
-1
View File
@@ -10,7 +10,6 @@ import {
uniq,
updateFile,
} from '@nx/e2e/utils';
import { join } from 'path';
describe('Node Applications + esbuild', () => {
beforeAll(() =>
+3 -1
View File
@@ -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');
+74 -26
View File
@@ -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<any> {
return new Promise((resolve) => {
http.get(`http://localhost:${port}${path}`, (res) => {
@@ -49,18 +55,23 @@ function getData(port, path = '/api'): Promise<any> {
}
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
+6 -2
View File
@@ -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();
}
+3 -2
View File
@@ -13,14 +13,15 @@ export const promisifiedTreeKill: (
export async function killPort(port: number): Promise<boolean> {
if (await portCheck(port)) {
let killPortResult;
try {
logInfo(`Attempting to close port ${port}`);
await kill(port);
killPortResult = await kill(port);
await new Promise<void>((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;
Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

+4 -1
View File
@@ -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*',
};
/**
@@ -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: '',
@@ -1,15 +1,75 @@
import { ButtonLink, SectionHeading } from '@nx/nx-dev/ui-common';
export function LaunchWeekAnnouncements(): JSX.Element {
return (
<div className="border-t border-slate-200 dark:border-slate-700">
<section className="w-full divide-y divide-slate-200 border-t border-b border-slate-200 dark:divide-slate-700 dark:border-slate-700">
<div className="mx-auto max-w-screen-lg xl:max-w-screen-xl">
<article className="md:divide-x md:divide-slate-200 md:dark:divide-slate-700">
<div className="px-5 py-12 md:pr-12">
<p>
Well 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!
</p>
<div className="border-y border-slate-200 dark:border-slate-700">
<section className="w-full divide-y divide-slate-200 dark:divide-slate-700">
<article className="mx-auto max-w-screen-lg xl:max-w-screen-xl">
<div className="px-5 py-12 md:pr-12">
<p>
Well 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!
</p>
</div>
</article>
{/* MONDAY */}
<div>
<article className="relative overflow-hidden pt-4 mx-auto max-w-screen-lg xl:max-w-screen-xl">
<div className="px-5 py-12 grid sm:grid-cols-2 sm:gap-8 lg:py-16">
<div>
<header>
<SectionHeading as="h2" variant="title" id="monday">
Monday
</SectionHeading>
<SectionHeading as="p" variant="display" className="mt-4">
Announcing Project Crystal
</SectionHeading>
</header>
<div className="mt-8 flex gap-16 font-normal">
<p className="max-w-xl text-lg text-slate-700 dark:text-slate-400">
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.
<br />
This is what Nx Project Crystal is all about.
</p>
</div>
<div className="action my-6 flex space-x-2">
<ButtonLink
variant="primary"
size="default"
href="https://blog.nrwl.io/what-if-nx-plugins-were-more-like-vscode-extensions-dcdad140ae09?source=friends_link&sk=ade76fe8d50d44aafb4d4d89ab882e24"
title="Unveiling Project Crystal"
>
Read the blog post
</ButtonLink>
<ButtonLink
variant="primary"
size="default"
href="https://youtu.be/wADNsVItnsM"
title="Nx - Project Crystal - Youtube Video"
>
Watch the video
</ButtonLink>
</div>
</div>
<div
aria-hidden="true"
className="order-first sm:order-last pb-8 relative flex flex-col items-center"
>
<img
className="rounded-lg"
src="/images/launch-nx/proj-crystal-launch.jpg"
alt="Nx Project Crystal"
/>
</div>
</div>
</article>
</div>
@@ -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`] = `
@@ -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;
@@ -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};`
);
}
+1 -1
View File
@@ -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.
-54
View File
@@ -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;
}
}
@@ -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;
}
+1 -11
View File
@@ -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(
+14 -11
View File
@@ -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<void> {
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<void> {
? await connectExistingRepoToNxCloudPrompt()
: false);
const repoRoot = process.cwd();
const pmc = getPackageManagerCommand();
createNxJsonFile(repoRoot, [], [], {});
updateGitIgnore(repoRoot);
addDepsToPackageJson(repoRoot, detectPluginsResponse?.plugins ?? []);
output.log({ title: '📦 Installing Nx' });
@@ -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",
},
}
`);
});
});
});
@@ -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<T extends Object = any>(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<ProjectConfiguration, 'targets'>,
packageJson: Pick<PackageJson, 'scripts' | 'nx'>
) {
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<ProjectConfiguration, 'targets'>,
packageJson: Pick<PackageJson, 'scripts' | 'nx'>
) {
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;
}
@@ -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: {