Compare commits

...

24 Commits

Author SHA1 Message Date
FrozenPandaz b024f44a1a chore(misc): publish 16.7.2 2023-08-18 16:07:25 -04:00
Craigory Coppola e4b64f5623 cleanup(angular): move special handling of angular.json to a builtin plugin (#18589)
(cherry picked from commit 253bb65051)
2023-08-18 13:04:00 -04:00
Miroslav Jonaš 30fac6187f chore(repo): fix version of berry for e2e tests (#18702)
(cherry picked from commit b8b1442c33)
2023-08-18 13:03:55 -04:00
Jason Jean f98d01eea8 chore(misc): fix yarn berry tests (#18694)
(cherry picked from commit f8893b6a8e)
2023-08-18 13:03:52 -04:00
Leosvel Pérez Espinosa 7578e4ba4d fix(js): expose schematic using the generator function with a default for the project name and root format (#18681)
(cherry picked from commit 61d73fc907)
2023-08-18 13:03:34 -04:00
Miroslav Jonaš 83011f747c fix(core): fix schema links (#18395)
(cherry picked from commit ded15436a4)
2023-08-18 13:03:31 -04:00
Miroslav Jonaš 95099aec61 fix(core): nx should not break if packages were not installed (#18687)
(cherry picked from commit 20acfbe681)
2023-08-18 13:03:28 -04:00
Craigory Coppola 1857e8f6f1 fix(devkit): convert-nx-executor should read projectNodes (#18684)
(cherry picked from commit 2ff1364579)
2023-08-18 13:03:18 -04:00
Craigory Coppola 894437bf61 cleanup(core): move package json script handling to package json plugin (#18659)
(cherry picked from commit 2d08229000)
2023-08-18 13:03:13 -04:00
Nicholas Cunningham bcaecfd902 fix(nextjs): make next build package manager agnostic (#18661)
(cherry picked from commit 2e1bccd0c3)
2023-08-18 13:03:02 -04:00
Craigory Coppola bd80c805d5 cleanup(devkit): correct description of disabling daemon inside createProjectGraph async jsdoc (#18627)
(cherry picked from commit 0b84bbcc75)
2023-08-18 13:02:57 -04:00
Craigory Coppola a459528819 fix(core): name collisions during project inference should not error out if corrected by a project.json's name (#18665)
(cherry picked from commit 94cc716a8a)
2023-08-18 13:02:30 -04:00
Miroslav Jonaš 76c96faa69 fix(core): use existing version when calling "yarn set version" (#18669)
(cherry picked from commit 7d55f49e7d)
2023-08-18 13:02:18 -04:00
Miroslav Jonaš f6a7f18143 fix(core): fix ghost alias when parsing yarn lockfile (#18646)
(cherry picked from commit 43b802d1cc)
2023-08-18 13:02:16 -04:00
jogelin da3ca7608c fix(linter): Normalize paths when in getSourceFilePath (#18601)
Co-authored-by: jgelin <jgelin@seligent.com>
(cherry picked from commit 1f9b3c4a5c)
2023-08-18 13:02:11 -04:00
Jason Jean 3b1a1b3523 chore(devkit): move all internal nx imports to requireNx (#18639)
(cherry picked from commit 2034cddf04)
2023-08-18 13:02:09 -04:00
FrozenPandaz 8b38708d6c chore(misc): publish 16.7.1 2023-08-16 18:12:23 -04:00
Jonathan Cammisuli 28c3fa3f43 fix(core): handle symlinks on debian for watch events (#18636)
(cherry picked from commit cf0b2fd4f4)
2023-08-16 15:54:13 -04:00
Jonathan Cammisuli 6a8a8a41ba fix(core): improve error handling for copy and remove (#18656)
(cherry picked from commit 13925ba576)
2023-08-16 15:54:11 -04:00
Jason Jean 0e6a353b33 fix(core): apply target defaults properly for executors defaults (#18655)
(cherry picked from commit f670e7469f)
2023-08-16 15:54:09 -04:00
Craigory Coppola 1a4c483a34 chore(repo): add .pnpm-store to gitignore (#18626)
(cherry picked from commit 52408960cd)
2023-08-16 15:53:16 -04:00
Craigory Coppola e2c0afeafb fix(core): use Date.now() everywhere for tasks start and end times to avoid negative durations (#18595)
(cherry picked from commit 98a7477922)
2023-08-16 15:53:10 -04:00
Craigory Coppola a5d6e0a852 fix(core): set consistent working directory when calculating runtime hash inputs (#18643)
(cherry picked from commit e2ac4e38e7)
2023-08-16 15:53:05 -04:00
Emily Xiong 14d48c624c chore(core): remove readProjectsConfigurations from workspaces (#18329)
Co-authored-by: FrozenPandaz <jasonjean1993@gmail.com>
(cherry picked from commit 40d66ec715)
2023-08-16 15:53:02 -04:00
105 changed files with 1479 additions and 875 deletions
+6
View File
@@ -19,7 +19,10 @@ jest.debug.config.js
/graph/client/src/assets/generated-task-graphs
/nx-dev/nx-dev/public/documentation
/nx-dev/nx-dev/public/images/open-graph
# Issues scraper creates these files, stored by github's cache
/scripts/issues-scraper/cached
# Lerna creates this
CHANGELOG.md
@@ -34,3 +37,6 @@ CHANGELOG.md
.bashrc
*.node
# Fix for issue when working on the repo in a dev container
.pnpm-store
Generated
+10
View File
@@ -1353,6 +1353,7 @@ dependencies = [
"napi-build",
"napi-derive",
"once_cell",
"os_type",
"rayon",
"regex",
"swc_common",
@@ -1378,6 +1379,15 @@ version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]]
name = "os_type"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e24d44c0eea30167516ed8f6daca4b5e3eebcde1bde1e4e6e08b809fb02c7ba5"
dependencies = [
"regex",
]
[[package]]
name = "overload"
version = "0.1.1"
@@ -7,7 +7,6 @@ Context for [CreateNodesFunction](../../devkit/documents/CreateNodesFunction)
### Properties
- [nxJsonConfiguration](../../devkit/documents/CreateNodesContext#nxjsonconfiguration)
- [projectsConfigurations](../../devkit/documents/CreateNodesContext#projectsconfigurations)
- [workspaceRoot](../../devkit/documents/CreateNodesContext#workspaceroot)
## Properties
@@ -18,12 +17,6 @@ Context for [CreateNodesFunction](../../devkit/documents/CreateNodesFunction)
---
### projectsConfigurations
`Readonly` **projectsConfigurations**: `Record`<`string`, [`ProjectConfiguration`](../../devkit/documents/ProjectConfiguration)\>
---
### workspaceRoot
`Readonly` **workspaceRoot**: `string`
+1 -1
View File
@@ -138,12 +138,12 @@ It only uses language primitives and immutable objects
- [offsetFromRoot](../../devkit/documents/offsetFromRoot)
- [parseJson](../../devkit/documents/parseJson)
- [parseTargetString](../../devkit/documents/parseTargetString)
- [readAllWorkspaceConfiguration](../../devkit/documents/readAllWorkspaceConfiguration)
- [readCachedProjectGraph](../../devkit/documents/readCachedProjectGraph)
- [readJson](../../devkit/documents/readJson)
- [readJsonFile](../../devkit/documents/readJsonFile)
- [readNxJson](../../devkit/documents/readNxJson)
- [readProjectConfiguration](../../devkit/documents/readProjectConfiguration)
- [readProjectsConfigurationFromProjectGraph](../../devkit/documents/readProjectsConfigurationFromProjectGraph)
- [readRootPackageJson](../../devkit/documents/readRootPackageJson)
- [readTargetOptions](../../devkit/documents/readTargetOptions)
- [readWorkspaceConfiguration](../../devkit/documents/readWorkspaceConfiguration)
+7 -54
View File
@@ -1,5 +1,9 @@
# Class: Workspaces
**`Deprecated`**
This will be removed in v18. Use [readProjectsConfigurationFromProjectGraph](../../devkit/documents/readProjectsConfigurationFromProjectGraph) instead.
## Table of contents
### Constructors
@@ -8,13 +12,10 @@
### Properties
- [cachedProjectsConfig](../../devkit/documents/Workspaces#cachedprojectsconfig)
- [root](../../devkit/documents/Workspaces#root)
### Methods
- [mergeTargetDefaultsIntoProjectDescriptions](../../devkit/documents/Workspaces#mergetargetdefaultsintoprojectdescriptions)
- [readProjectsConfigurations](../../devkit/documents/Workspaces#readprojectsconfigurations)
- [readWorkspaceConfiguration](../../devkit/documents/Workspaces#readworkspaceconfiguration)
## Constructors
@@ -31,67 +32,19 @@
## Properties
### cachedProjectsConfig
`Private` **cachedProjectsConfig**: [`ProjectsConfigurations`](../../devkit/documents/ProjectsConfigurations)
---
### root
`Private` **root**: `string`
## Methods
### mergeTargetDefaultsIntoProjectDescriptions
### readWorkspaceConfiguration
`Private` **mergeTargetDefaultsIntoProjectDescriptions**(`projects`, `nxJson`): `Record`<`string`, [`ProjectConfiguration`](../../devkit/documents/ProjectConfiguration)\>
#### Parameters
| Name | Type |
| :--------- | :----------------------------------------------------------------------------------------- |
| `projects` | `Record`<`string`, [`ProjectConfiguration`](../../devkit/documents/ProjectConfiguration)\> |
| `nxJson` | [`NxJsonConfiguration`](../../devkit/documents/NxJsonConfiguration)<`string`[] \| `"*"`\> |
#### Returns
`Record`<`string`, [`ProjectConfiguration`](../../devkit/documents/ProjectConfiguration)\>
---
### readProjectsConfigurations
**readProjectsConfigurations**(`opts?`): [`ProjectsConfigurations`](../../devkit/documents/ProjectsConfigurations)
**readWorkspaceConfiguration**(): [`ProjectsConfigurations`](../../devkit/documents/ProjectsConfigurations) & [`NxJsonConfiguration`](../../devkit/documents/NxJsonConfiguration)<`string`[] \| `"*"`\>
**`Deprecated`**
#### Parameters
| Name | Type |
| :-------------------------------------- | :-------- |
| `opts?` | `Object` |
| `opts._includeProjectsFromAngularJson?` | `boolean` |
#### Returns
[`ProjectsConfigurations`](../../devkit/documents/ProjectsConfigurations)
---
### readWorkspaceConfiguration
**readWorkspaceConfiguration**(`opts?`): [`ProjectsConfigurations`](../../devkit/documents/ProjectsConfigurations) & [`NxJsonConfiguration`](../../devkit/documents/NxJsonConfiguration)<`string`[] \| `"*"`\>
Deprecated. Use readProjectsConfigurations
#### Parameters
| Name | Type |
| :-------------------------------------- | :-------- |
| `opts?` | `Object` |
| `opts._ignorePluginInference?` | `boolean` |
| `opts._includeProjectsFromAngularJson?` | `boolean` |
Use [readProjectsConfigurationFromProjectGraph](../../devkit/documents/readProjectsConfigurationFromProjectGraph) instead.
#### Returns
@@ -12,7 +12,7 @@ Nx will compute it in the current process if:
- It is running in the docker container.
- The daemon process is disabled because of the previous error when starting the daemon.
- `NX_DAEMON` is set to `false`.
- `useDaemon` is set to false in `nx.json`
- `useDaemonProcess` is set to false in the options of the tasks runner inside `nx.json`
`NX_DAEMON` env variable takes precedence:
@@ -1,11 +0,0 @@
# Function: readAllWorkspaceConfiguration
**readAllWorkspaceConfiguration**(): [`ProjectsConfigurations`](../../devkit/documents/ProjectsConfigurations) & [`NxJsonConfiguration`](../../devkit/documents/NxJsonConfiguration)
**`Deprecated`**
Use readProjectsConfigurationFromProjectGraph(await createProjectGraphAsync())
#### Returns
[`ProjectsConfigurations`](../../devkit/documents/ProjectsConfigurations) & [`NxJsonConfiguration`](../../devkit/documents/NxJsonConfiguration)
@@ -0,0 +1,15 @@
# Function: readProjectsConfigurationFromProjectGraph
**readProjectsConfigurationFromProjectGraph**(`projectGraph`): [`ProjectsConfigurations`](../../devkit/documents/ProjectsConfigurations)
Get the [ProjectsConfigurations](../../devkit/documents/ProjectsConfigurations) from the [ProjectGraph](../../devkit/documents/ProjectGraph)
#### Parameters
| Name | Type |
| :------------- | :---------------------------------------------------- |
| `projectGraph` | [`ProjectGraph`](../../devkit/documents/ProjectGraph) |
#### Returns
[`ProjectsConfigurations`](../../devkit/documents/ProjectsConfigurations)
@@ -138,12 +138,12 @@ It only uses language primitives and immutable objects
- [offsetFromRoot](../../devkit/documents/offsetFromRoot)
- [parseJson](../../devkit/documents/parseJson)
- [parseTargetString](../../devkit/documents/parseTargetString)
- [readAllWorkspaceConfiguration](../../devkit/documents/readAllWorkspaceConfiguration)
- [readCachedProjectGraph](../../devkit/documents/readCachedProjectGraph)
- [readJson](../../devkit/documents/readJson)
- [readJsonFile](../../devkit/documents/readJsonFile)
- [readNxJson](../../devkit/documents/readNxJson)
- [readProjectConfiguration](../../devkit/documents/readProjectConfiguration)
- [readProjectsConfigurationFromProjectGraph](../../devkit/documents/readProjectsConfigurationFromProjectGraph)
- [readRootPackageJson](../../devkit/documents/readRootPackageJson)
- [readTargetOptions](../../devkit/documents/readTargetOptions)
- [readWorkspaceConfiguration](../../devkit/documents/readWorkspaceConfiguration)
@@ -173,8 +173,10 @@ describe('Angular Module Federation', () => {
// ports
const hostPort = 4500;
const remote1Port = readProjectConfig(remote1).targets.serve.options.port;
const remote2Port = readProjectConfig(remote2).targets.serve.options.port;
const remote1Port = (await readProjectConfig(remote1)).targets.serve.options
.port;
const remote2Port = (await readProjectConfig(remote2)).targets.serve.options
.port;
const process = await runCommandUntil(
`serve-ssr ${host} --port=${hostPort}`,
+2 -2
View File
@@ -268,7 +268,7 @@ describe('Angular Projects', () => {
);
// update the angular.json
updateProjectConfig(app1, (config) => {
await updateProjectConfig(app1, (config) => {
config.targets.build.executor = '@nx/angular:webpack-browser';
config.targets.build.options = {
...config.targets.build.options,
@@ -276,7 +276,7 @@ describe('Angular Projects', () => {
};
return config;
});
updateProjectConfig(esbuildApp, (config) => {
await updateProjectConfig(esbuildApp, (config) => {
config.targets.build.executor = '@nx/angular:browser-esbuild';
config.targets.build.options = {
...config.targets.build.options,
@@ -30,7 +30,7 @@ describe('Angular Cypress Component Tests', () => {
createBuildableLib(projectName, buildableLibName);
useWorkspaceAssetsInApp(appName);
await useWorkspaceAssetsInApp(appName);
});
afterAll(() => cleanupProject());
@@ -265,7 +265,7 @@ export class AppModule {}
);
}
function useWorkspaceAssetsInApp(appName: string) {
async function useWorkspaceAssetsInApp(appName: string) {
// make sure assets from the workspace root work.
createFile('libs/assets/data.json', JSON.stringify({ data: 'data' }));
createFile(
@@ -279,7 +279,7 @@ function useWorkspaceAssetsInApp(appName: string) {
}
`
);
updateProjectConfig(appName, (config) => {
await updateProjectConfig(appName, (config) => {
config.targets['build'].options.stylePreprocessorOptions = {
includePaths: ['assets/styles'],
};
+1 -1
View File
@@ -360,7 +360,7 @@ describe('Tailwind support', () => {
runCLI(
`generate @nx/angular:app ${appWithTailwind} --add-tailwind --no-interactive`
);
updateProjectConfig(appWithTailwind, (config) => {
await updateProjectConfig(appWithTailwind, (config) => {
config.targets.build.executor = '@nx/angular:webpack-browser';
config.targets.build.options = {
...config.targets.build.options,
+4 -5
View File
@@ -6,7 +6,6 @@ import {
packageInstall,
readFile,
readJson,
rmDist,
runCLI,
runCommand,
runCommandUntil,
@@ -27,7 +26,7 @@ describe('EsBuild Plugin', () => {
const myPkg = uniq('my-pkg');
runCLI(`generate @nx/js:lib ${myPkg} --bundler=esbuild`);
updateFile(`libs/${myPkg}/src/index.ts`, `console.log('Hello');\n`);
updateProjectConfig(myPkg, (json) => {
await updateProjectConfig(myPkg, (json) => {
json.targets.build.options.assets = [`libs/${myPkg}/assets/*`];
return json;
});
@@ -179,12 +178,12 @@ describe('EsBuild Plugin', () => {
expect(runCommand(`node dist/libs/${myPkg}`)).toMatch(/Hello/);
}, 300_000);
it('should support additional entry points', () => {
it('should support additional entry points', async () => {
const myPkg = uniq('my-pkg');
runCLI(`generate @nx/js:lib ${myPkg} --bundler=esbuild`);
updateFile(`libs/${myPkg}/src/index.ts`, `console.log('main');\n`);
updateFile(`libs/${myPkg}/src/extra.ts`, `console.log('extra');\n`);
updateProjectConfig(myPkg, (json) => {
await updateProjectConfig(myPkg, (json) => {
json.targets.build.options.additionalEntryPoints = [
`libs/${myPkg}/src/extra.ts`,
];
@@ -212,7 +211,7 @@ describe('EsBuild Plugin', () => {
`libs/${myPkg}/esbuild.config.js`,
`console.log('custom config loaded');\nmodule.exports = {};\n`
);
updateProjectConfig(myPkg, (json) => {
await updateProjectConfig(myPkg, (json) => {
delete json.targets.build.options.esbuildOptions;
json.targets.build.options.esbuildConfig = `libs/${myPkg}/esbuild.config.js`;
return json;
+2 -2
View File
@@ -85,8 +85,8 @@ describe('expo', () => {
it('should prebuild', async () => {
// run prebuild command with git check disable
// set a mock package name for ios and android in expo's app.json
const workspace = readResolvedConfiguration();
const root = workspace.projects[appName].root;
const projects = await readResolvedConfiguration();
const root = projects[appName].root;
const appJsonPath = join(root, `app.json`);
const appJson = await readJson(appJsonPath);
if (appJson.expo.ios) {
+10 -10
View File
@@ -16,7 +16,7 @@ describe('js:node error handling', () => {
afterEach(() => cleanupProject());
it('should log out the error', () => {
it('should log out the error', async () => {
const esbuildLib = uniq('esbuildlib');
runCLI(
@@ -30,7 +30,7 @@ describe('js:node error handling', () => {
`;
});
updateProjectConfig(esbuildLib, (config) => {
await updateProjectConfig(esbuildLib, (config) => {
config.targets['run-node'] = {
executor: '@nx/js:node',
options: {
@@ -48,7 +48,7 @@ describe('js:node error handling', () => {
expect(output).toContain('This is an error');
}, 240_000);
it('should execute library compiled with rollup', () => {
it('should execute library compiled with rollup', async () => {
const rollupLib = uniq('rolluplib');
runCLI(
@@ -61,7 +61,7 @@ describe('js:node error handling', () => {
`;
});
updateProjectConfig(rollupLib, (config) => {
await updateProjectConfig(rollupLib, (config) => {
config.targets['run-node'] = {
executor: '@nx/js:node',
options: {
@@ -76,7 +76,7 @@ describe('js:node error handling', () => {
expect(output).toContain('Hello from my library!');
}, 240_000);
it('should execute library compiled with tsc', () => {
it('should execute library compiled with tsc', async () => {
const tscLib = uniq('tsclib');
runCLI(`generate @nx/js:lib ${tscLib} --bundler=tsc --no-interactive`);
@@ -87,7 +87,7 @@ describe('js:node error handling', () => {
`;
});
updateProjectConfig(tscLib, (config) => {
await updateProjectConfig(tscLib, (config) => {
config.targets['run-node'] = {
executor: '@nx/js:node',
options: {
@@ -102,7 +102,7 @@ describe('js:node error handling', () => {
expect(output).toContain('Hello from my tsc library!');
}, 240_000);
it('should execute library compiled with swc', () => {
it('should execute library compiled with swc', async () => {
const swcLib = uniq('swclib');
runCLI(`generate @nx/js:lib ${swcLib} --bundler=swc --no-interactive`);
@@ -113,7 +113,7 @@ describe('js:node error handling', () => {
`;
});
updateProjectConfig(swcLib, (config) => {
await updateProjectConfig(swcLib, (config) => {
config.targets['run-node'] = {
executor: '@nx/js:node',
options: {
@@ -128,7 +128,7 @@ describe('js:node error handling', () => {
expect(output).toContain('Hello from my swc library!');
}, 240_000);
it('should execute webpack app', () => {
it('should execute webpack app', async () => {
const webpackProject = uniq('webpackproject');
runCLI(
@@ -141,7 +141,7 @@ describe('js:node error handling', () => {
`;
});
updateProjectConfig(webpackProject, (config) => {
await updateProjectConfig(webpackProject, (config) => {
config.targets['run-node'] = {
executor: '@nx/js:node',
options: {
+3 -3
View File
@@ -123,7 +123,7 @@ describe('bundling libs', () => {
expect(output).toContain(rollupLib);
}, 500_000);
it('should support tsc and swc for building libs', () => {
it('should support tsc and swc for building libs', async () => {
const tscLib = uniq('tsclib');
const swcLib = uniq('swclib');
const tscEsmLib = uniq('tscesmlib');
@@ -154,7 +154,7 @@ describe('bundling libs', () => {
);
// Add additional entry points for `exports` field
updateProjectConfig(tscLib, (json) => {
await updateProjectConfig(tscLib, (json) => {
json.targets.build.options.additionalEntryPoints = [
`libs/${tscLib}/src/foo/*.ts`,
];
@@ -162,7 +162,7 @@ describe('bundling libs', () => {
});
updateFile(`libs/${tscLib}/src/foo/bar.ts`, `export const bar = 'bar';`);
updateFile(`libs/${tscLib}/src/foo/faz.ts`, `export const faz = 'faz';`);
updateProjectConfig(swcLib, (json) => {
await updateProjectConfig(swcLib, (json) => {
json.targets.build.options.additionalEntryPoints = [
`libs/${swcLib}/src/foo/*.ts`,
];
+2 -2
View File
@@ -67,7 +67,7 @@ describe('Next.js Applications', () => {
// Additional assets that should be copied to dist
const sharedLib = uniq('sharedLib');
updateProjectConfig(appName, (json) => {
await updateProjectConfig(appName, (json) => {
json.targets.build.options.assets = [
{
glob: '**/*',
@@ -367,7 +367,7 @@ describe('Next.js Applications', () => {
checkFilesExist(`dist/apps/${appName}/next.config.js`);
// Make sure withNx works with run-commands.
updateProjectConfig(appName, (json) => {
await updateProjectConfig(appName, (json) => {
json.targets.build = {
command: 'npx next build',
outputs: [`apps/${appName}/.next`],
+1 -1
View File
@@ -151,7 +151,7 @@ describe('Node Applications + webpack', () => {
runCLI(
`generate @nx/node:app ${nodeApp2} --framework=none --no-interactive`
);
updateProjectConfig(nodeApp1, (config) => {
await updateProjectConfig(nodeApp1, (config) => {
config.targets.serve.options.waitUntilTargets = [`${nodeApp2}:build`];
return config;
});
+1 -1
View File
@@ -55,7 +55,7 @@ describe('Node Applications + webpack', () => {
const lib = uniq('nodelib');
runCLI(`generate @nx/js:lib ${lib} --bundler=esbuild --no-interactive`);
updateProjectConfig(app, (config) => {
await updateProjectConfig(app, (config) => {
// Since we read from lib from dist, we should re-build it when lib changes.
config.targets.build.options.buildLibsFromSource = false;
config.targets.serve.options.runBuildTargetDependencies = true;
+5 -5
View File
@@ -75,7 +75,7 @@ describe('Node Applications', () => {
const nodeapp = uniq('nodeapp');
runCLI(`generate @nx/node:app ${nodeapp} --linter=eslint`);
updateProjectConfig(nodeapp, (config) => {
await updateProjectConfig(nodeapp, (config) => {
config.targets.build.options.outputFileName = 'index.js';
return config;
});
@@ -94,7 +94,7 @@ describe('Node Applications', () => {
const lintResults = runCLI(`lint ${nodeapp}`);
expect(lintResults).toContain('All files pass linting.');
updateProjectConfig(nodeapp, (config) => {
await updateProjectConfig(nodeapp, (config) => {
config.targets.build.options.additionalEntryPoints = [
{
entryName: 'additional-main',
@@ -254,7 +254,7 @@ describe('Node Applications', () => {
config.target = 'es2020';
return config;
});
updateProjectConfig(esmapp, (config) => {
await updateProjectConfig(esmapp, (config) => {
config.targets.build.options.outputFileName = 'main.mjs';
config.targets.build.options.assets = [];
return config;
@@ -436,7 +436,7 @@ ${jslib}();
packageInstall('@nestjs/swagger', undefined, '^6.0.0');
updateProjectConfig(nestapp, (config) => {
await updateProjectConfig(nestapp, (config) => {
config.targets.build.options.tsPlugins = ['@nestjs/swagger/plugin'];
return config;
});
@@ -549,7 +549,7 @@ describe('nest libraries', function () {
packageInstall('@nestjs/swagger', undefined, '~6.3.0');
updateProjectConfig(nestlib, (config) => {
await updateProjectConfig(nestlib, (config) => {
config.targets.build.options.transformers = [
{
name: '@nestjs/swagger/plugin',
+7 -7
View File
@@ -18,7 +18,7 @@ describe('Extra Nx Misc Tests', () => {
it('should stream output', async () => {
const myapp = 'abcdefghijklmon';
runCLI(`generate @nx/web:app ${myapp}`);
updateProjectConfig(myapp, (c) => {
await updateProjectConfig(myapp, (c) => {
c.targets['inner'] = {
command: 'echo inner',
};
@@ -122,7 +122,7 @@ describe('Extra Nx Misc Tests', () => {
process.platform === 'win32'
? `%SHARED_VAR% %ROOT_ONLY% %NESTED_ONLY%` // Windows
: `$SHARED_VAR $ROOT_ONLY $NESTED_ONLY`;
updateProjectConfig(mylib, (config) => {
await updateProjectConfig(mylib, (config) => {
config.targets.echoEnvVariables.options.command += ` ${command}`;
return config;
});
@@ -135,7 +135,7 @@ describe('Extra Nx Misc Tests', () => {
}, 120000);
it('should pass options', async () => {
updateProjectConfig(mylib, (config) => {
await updateProjectConfig(mylib, (config) => {
config.targets.echo = {
command: 'echo --var1={args.var1}',
options: {
@@ -151,7 +151,7 @@ describe('Extra Nx Misc Tests', () => {
it('should interpolate provided arguments', async () => {
const echoTarget = uniq('echo');
updateProjectConfig(mylib, (config) => {
await updateProjectConfig(mylib, (config) => {
config.targets[echoTarget] = {
executor: 'nx:run-commands',
options: {
@@ -186,7 +186,7 @@ describe('Extra Nx Misc Tests', () => {
}, 120000);
it('should fail when a process exits non-zero', async () => {
updateProjectConfig(mylib, (config) => {
await updateProjectConfig(mylib, (config) => {
config.targets.error = {
executor: 'nx:run-commands',
options: {
@@ -207,7 +207,7 @@ describe('Extra Nx Misc Tests', () => {
});
it('run command should not break if output property is missing in options and arguments', async () => {
updateProjectConfig(mylib, (config) => {
await updateProjectConfig(mylib, (config) => {
config.targets.lint.outputs = ['{options.outputFile}'];
return config;
});
@@ -241,7 +241,7 @@ describe('Extra Nx Misc Tests', () => {
: `mkdir -p ${folder}`,
`echo dummy > ${folder}/dummy.txt`,
];
updateProjectConfig(mylib, (config) => {
await updateProjectConfig(mylib, (config) => {
delete config.targets.build.options.command;
config.targets.build.options = {
...config.targets.build.options,
+36 -36
View File
@@ -113,7 +113,7 @@ describe('Workspace Tests', () => {
*/
runCLI(`generate @nx/js:lib ${lib3} --unitTestRunner=jest`);
updateProjectConfig(lib3, (config) => {
await updateProjectConfig(lib3, (config) => {
config.implicitDependencies = [`${lib1}-data-access`];
return config;
});
@@ -172,13 +172,13 @@ describe('Workspace Tests', () => {
expect(moveOutput).toContain(`CREATE ${rootClassPath}`);
checkFilesExist(rootClassPath);
let workspace = await readResolvedConfiguration();
expect(workspace.projects[`${lib1}-data-access`]).toBeUndefined();
const newConfig = readProjectConfig(newName);
let projects = await readResolvedConfiguration();
expect(projects[`${lib1}-data-access`]).toBeUndefined();
const newConfig = await readProjectConfig(newName);
expect(newConfig).toMatchObject({
tags: [],
});
const lib3Config = readProjectConfig(lib3);
const lib3Config = await readProjectConfig(lib3);
expect(lib3Config.implicitDependencies).toEqual([
`shared-${lib1}-data-access`,
]);
@@ -194,9 +194,9 @@ describe('Workspace Tests', () => {
]
).toEqual([`libs/shared/${lib1}/data-access/src/index.ts`]);
workspace = readResolvedConfiguration();
expect(workspace.projects[`${lib1}-data-access`]).toBeUndefined();
const project = readProjectConfig(newName);
projects = await readResolvedConfiguration();
expect(projects[`${lib1}-data-access`]).toBeUndefined();
const project = await readProjectConfig(newName);
expect(project).toBeTruthy();
expect(project.sourceRoot).toBe(`${newPath}/src`);
expect(project.targets.lint.options.lintFilePatterns).toEqual([
@@ -251,7 +251,7 @@ describe('Workspace Tests', () => {
*/
runCLI(`generate @nx/js:lib ${lib3} --unitTestRunner=jest`);
updateProjectConfig(lib3, (config) => {
await updateProjectConfig(lib3, (config) => {
config.implicitDependencies = [`${lib1}-data-access`];
return config;
});
@@ -321,13 +321,13 @@ describe('Workspace Tests', () => {
]
).toEqual([`libs/shared/${lib1}/data-access/src/index.ts`]);
const workspace = await readResolvedConfiguration();
expect(workspace.projects[`${lib1}-data-access`]).toBeUndefined();
const project = readProjectConfig(newName);
const projects = await readResolvedConfiguration();
expect(projects[`${lib1}-data-access`]).toBeUndefined();
const project = await readProjectConfig(newName);
expect(project).toBeTruthy();
expect(project.sourceRoot).toBe(`${newPath}/src`);
expect(project.tags).toEqual([]);
const lib3Config = readProjectConfig(lib3);
const lib3Config = await readProjectConfig(lib3);
expect(lib3Config.implicitDependencies).toEqual([newName]);
expect(project.targets.lint.options.lintFilePatterns).toEqual([
@@ -384,7 +384,7 @@ describe('Workspace Tests', () => {
*/
runCLI(`generate @nx/js:lib ${lib3} --unitTestRunner=jest`);
updateProjectConfig(lib3, (config) => {
await updateProjectConfig(lib3, (config) => {
config.implicitDependencies = [`${lib1}-data-access`];
return config;
});
@@ -454,9 +454,9 @@ describe('Workspace Tests', () => {
]
).toEqual([`packages/shared/${lib1}/data-access/src/index.ts`]);
const workspace = await readResolvedConfiguration();
expect(workspace.projects[`${lib1}-data-access`]).toBeUndefined();
const project = readProjectConfig(newName);
const projects = await readResolvedConfiguration();
expect(projects[`${lib1}-data-access`]).toBeUndefined();
const project = await readProjectConfig(newName);
expect(project).toBeTruthy();
expect(project.sourceRoot).toBe(`${newPath}/src`);
expect(project.targets.lint.options.lintFilePatterns).toEqual([
@@ -513,7 +513,7 @@ describe('Workspace Tests', () => {
*/
runCLI(`generate @nx/js:lib ${lib3} --unitTestRunner=jest`);
updateProjectConfig(lib3, (config) => {
await updateProjectConfig(lib3, (config) => {
config.implicitDependencies = [lib1];
return config;
});
@@ -572,13 +572,13 @@ describe('Workspace Tests', () => {
expect(moveOutput).toContain(`CREATE ${rootClassPath}`);
checkFilesExist(rootClassPath);
let workspace = readResolvedConfiguration();
expect(workspace.projects[lib1]).toBeUndefined();
const newConfig = readProjectConfig(newName);
let projects = await readResolvedConfiguration();
expect(projects[lib1]).toBeUndefined();
const newConfig = await readProjectConfig(newName);
expect(newConfig).toMatchObject({
tags: [],
});
const lib3Config = readProjectConfig(lib3);
const lib3Config = await readProjectConfig(lib3);
expect(lib3Config.implicitDependencies).toEqual([`${lib1}-data-access`]);
expect(moveOutput).toContain('UPDATE tsconfig.base.json');
@@ -590,9 +590,9 @@ describe('Workspace Tests', () => {
rootTsConfig.compilerOptions.paths[`@${proj}/${lib1}/data-access`]
).toEqual([`libs/${lib1}/data-access/src/index.ts`]);
workspace = readResolvedConfiguration();
expect(workspace.projects[lib1]).toBeUndefined();
const project = readProjectConfig(newName);
projects = await readResolvedConfiguration();
expect(projects[lib1]).toBeUndefined();
const project = await readProjectConfig(newName);
expect(project).toBeTruthy();
expect(project.sourceRoot).toBe(`${newPath}/src`);
expect(project.targets.lint.options.lintFilePatterns).toEqual([
@@ -655,7 +655,7 @@ describe('Workspace Tests', () => {
*/
runCLI(`generate @nx/js:lib ${lib3} --unitTestRunner=jest`);
updateProjectConfig(lib3, (config) => {
await updateProjectConfig(lib3, (config) => {
config.implicitDependencies = [`${lib1}-data-access`];
return config;
});
@@ -686,11 +686,11 @@ describe('Workspace Tests', () => {
expect(moveOutput).toContain(`CREATE ${rootClassPath}`);
checkFilesExist(rootClassPath);
const newConfig = readProjectConfig(newName);
const newConfig = await readProjectConfig(newName);
expect(newConfig).toMatchObject({
tags: [],
});
const lib3Config = readProjectConfig(lib3);
const lib3Config = await readProjectConfig(lib3);
expect(lib3Config.implicitDependencies).toEqual([
`shared-${lib1}-data-access`,
]);
@@ -704,9 +704,9 @@ describe('Workspace Tests', () => {
rootTsConfig.compilerOptions.paths[`shared/${lib1}/data-access`]
).toEqual([`libs/shared/${lib1}/data-access/src/index.ts`]);
const projects = readResolvedConfiguration();
expect(projects.projects[`${lib1}-data-access`]).toBeUndefined();
const project = readProjectConfig(newName);
const projects = await readResolvedConfiguration();
expect(projects[`${lib1}-data-access`]).toBeUndefined();
const project = await readProjectConfig(newName);
expect(project).toBeTruthy();
expect(project.sourceRoot).toBe(`${newPath}/src`);
expect(project.targets.lint.options.lintFilePatterns).toEqual([
@@ -741,7 +741,7 @@ describe('Workspace Tests', () => {
*/
runCLI(`generate @nx/js:lib ${lib2} --unitTestRunner=jest`);
updateProjectConfig(lib2, (config) => {
await updateProjectConfig(lib2, (config) => {
config.implicitDependencies = [lib1];
return config;
});
@@ -775,12 +775,12 @@ describe('Workspace Tests', () => {
expect(exists(tmpProjPath(`libs/${lib1}`))).toBeFalsy();
expect(removeOutputForced).not.toContain(`UPDATE nx.json`);
const projectsConfigurations = readResolvedConfiguration();
expect(projectsConfigurations.projects[`${lib1}`]).toBeUndefined();
const lib2Config = readProjectConfig(lib2);
const projects = await readResolvedConfiguration();
expect(projects[`${lib1}`]).toBeUndefined();
const lib2Config = await readProjectConfig(lib2);
expect(lib2Config.implicitDependencies).toEqual([]);
expect(projectsConfigurations.projects[`${lib1}`]).toBeUndefined();
expect(projects[`${lib1}`]).toBeUndefined();
});
});
});
+7 -7
View File
@@ -214,11 +214,11 @@ describe('Nx Affected and Graph Tests', () => {
}
}, 1000000);
it('should detect changes to projects based on tags changes', () => {
it('should detect changes to projects based on tags changes', async () => {
// TODO: investigate why affected gives different results on windows
if (isNotWindows()) {
generateAll();
updateProjectConfig(myapp, (config) => ({
await updateProjectConfig(myapp, (config) => ({
...config,
tags: ['tag'],
}));
@@ -229,9 +229,9 @@ describe('Nx Affected and Graph Tests', () => {
}
});
it('should affect all projects by removing projects', () => {
it('should affect all projects by removing projects', async () => {
generateAll();
const root = readResolvedConfiguration().projects[mylib].root;
const root = (await readResolvedConfiguration())[mylib].root;
removeFile(root);
const output = runCLI('print-affected --select projects');
expect(output).toContain(myapp);
@@ -239,9 +239,9 @@ describe('Nx Affected and Graph Tests', () => {
expect(output).not.toContain(mylib);
});
it('should detect changes to implicitly dependant projects', () => {
it('should detect changes to implicitly dependant projects', async () => {
generateAll();
updateProjectConfig(myapp, (config) => ({
await updateProjectConfig(myapp, (config) => ({
...config,
implicitDependencies: ['*', `!${myapp2}`],
}));
@@ -256,7 +256,7 @@ describe('Nx Affected and Graph Tests', () => {
expect(output).toContain(mylib);
// Clear implicit deps to not interfere with other tests.
updateProjectConfig(myapp, (config) => ({
await updateProjectConfig(myapp, (config) => ({
...config,
implicitDependencies: [],
}));
+1 -1
View File
@@ -156,7 +156,7 @@ describe('cache', () => {
it('should support using globs as outputs', async () => {
const mylib = uniq('mylib');
runCLI(`generate @nx/js:library ${mylib}`);
updateProjectConfig(mylib, (c) => {
await updateProjectConfig(mylib, (c) => {
c.targets.build = {
executor: 'nx:run-commands',
outputs: ['{workspaceRoot}/dist/!(.next)/**/!(z|x).(txt|md)'],
+1 -1
View File
@@ -16,7 +16,7 @@ describe('Invoke Runner', () => {
it('should invoke runner imperatively ', async () => {
const mylib = uniq('mylib');
runCLI(`generate @nx/js:lib ${mylib}`);
updateProjectConfig(mylib, (c) => {
await updateProjectConfig(mylib, (c) => {
c.targets['prebuild'] = {
command: 'echo prebuild',
};
+18 -18
View File
@@ -28,9 +28,9 @@ describe('Nx Running Tests', () => {
describe('running targets', () => {
describe('(forwarding params)', () => {
let proj = uniq('proj');
beforeAll(() => {
beforeAll(async () => {
runCLI(`generate @nx/js:lib ${proj}`);
updateProjectConfig(proj, (c) => {
await updateProjectConfig(proj, (c) => {
c.targets['echo'] = {
command: 'echo ECHO:',
};
@@ -57,7 +57,7 @@ describe('Nx Running Tests', () => {
it('should execute long running tasks', async () => {
const myapp = uniq('myapp');
runCLI(`generate @nx/web:app ${myapp}`);
updateProjectConfig(myapp, (c) => {
await updateProjectConfig(myapp, (c) => {
c.targets['counter'] = {
executor: '@nx/workspace:counter',
options: {
@@ -79,8 +79,8 @@ describe('Nx Running Tests', () => {
runCLI(`generate @nx/node:lib ${mylib}`);
// Used to restore targets to lib after test
const original = readProjectConfig(mylib);
updateProjectConfig(mylib, (j) => {
const original = await readProjectConfig(mylib);
await updateProjectConfig(mylib, (j) => {
delete j.targets;
return j;
});
@@ -113,7 +113,7 @@ describe('Nx Running Tests', () => {
`Cannot find configuration for task ${mylib}:echo:fail`
);
updateProjectConfig(mylib, (c) => original);
await updateProjectConfig(mylib, (c) => original);
}, 1000000);
describe('tokens support', () => {
@@ -125,7 +125,7 @@ describe('Nx Running Tests', () => {
});
it('should support using {projectRoot} in options blocks in project.json', async () => {
updateProjectConfig(app, (c) => {
await updateProjectConfig(app, (c) => {
c.targets['echo'] = {
command: `node -e 'console.log("{projectRoot}")'`,
};
@@ -137,7 +137,7 @@ describe('Nx Running Tests', () => {
});
it('should support using {projectName} in options blocks in project.json', async () => {
updateProjectConfig(app, (c) => {
await updateProjectConfig(app, (c) => {
c.targets['echo'] = {
command: `node -e 'console.log("{projectName}")'`,
};
@@ -157,7 +157,7 @@ describe('Nx Running Tests', () => {
};
return json;
});
updateProjectConfig(app, (c) => {
await updateProjectConfig(app, (c) => {
c.targets['echo'] = {};
return c;
});
@@ -174,7 +174,7 @@ describe('Nx Running Tests', () => {
};
return json;
});
updateProjectConfig(app, (c) => {
await updateProjectConfig(app, (c) => {
c.targets['echo'] = {};
return c;
});
@@ -190,13 +190,13 @@ describe('Nx Running Tests', () => {
const myapp2 = uniq('b');
runCLI(`generate @nx/web:app ${myapp1}`);
runCLI(`generate @nx/web:app ${myapp2}`);
updateProjectConfig(myapp1, (c) => {
await updateProjectConfig(myapp1, (c) => {
c.targets['error'] = {
command: 'echo boom1 && exit 1',
};
return c;
});
updateProjectConfig(myapp2, (c) => {
await updateProjectConfig(myapp2, (c) => {
c.targets['error'] = {
executor: 'nx:run-commands',
options: {
@@ -431,9 +431,9 @@ describe('Nx Running Tests', () => {
);
});
it('should be able to include deps using dependsOn', () => {
const originalWorkspace = readProjectConfig(myapp);
updateProjectConfig(myapp, (config) => {
it('should be able to include deps using dependsOn', async () => {
const originalWorkspace = await readProjectConfig(myapp);
await updateProjectConfig(myapp, (config) => {
config.targets.prep = {
executor: 'nx:run-commands',
options: {
@@ -453,12 +453,12 @@ describe('Nx Running Tests', () => {
expect(output).toContain(mylib2);
expect(output).toContain('PREP');
updateProjectConfig(myapp, () => originalWorkspace);
await updateProjectConfig(myapp, () => originalWorkspace);
}, 10000);
it('should be able to include deps using target defaults defined at the root', () => {
it('should be able to include deps using target defaults defined at the root', async () => {
const nxJson = readJson('nx.json');
updateProjectConfig(myapp, (config) => {
await updateProjectConfig(myapp, (config) => {
config.targets.prep = {
command: 'echo PREP > one.txt',
};
+4 -4
View File
@@ -381,14 +381,14 @@ describe('Nx Plugin', () => {
});
describe('--directory', () => {
it('should create a plugin in the specified directory', () => {
it('should create a plugin in the specified directory', async () => {
const plugin = uniq('plugin');
runCLI(
`generate @nx/plugin:plugin ${plugin} --linter=eslint --directory subdir --e2eTestRunner=jest`
);
checkFilesExist(`libs/subdir/${plugin}/package.json`);
const pluginProject = readProjectConfig(`subdir-${plugin}`);
const pluginE2EProject = readProjectConfig(`subdir-${plugin}-e2e`);
const pluginProject = await readProjectConfig(`subdir-${plugin}`);
const pluginE2EProject = await readProjectConfig(`subdir-${plugin}-e2e`);
expect(pluginProject.targets).toBeDefined();
expect(pluginE2EProject).toBeTruthy();
}, 90000);
@@ -399,7 +399,7 @@ describe('Nx Plugin', () => {
runCLI(
`generate @nx/plugin:plugin ${plugin} --linter=eslint --tags=e2etag,e2ePackage `
);
const pluginProject = readProjectConfig(plugin);
const pluginProject = await readProjectConfig(plugin);
expect(pluginProject.tags).toEqual(['e2etag', 'e2ePackage']);
}, 90000);
});
@@ -114,8 +114,8 @@ describe('React Module Federation', () => {
// }
}, 500_000);
function readPort(appName: string): number {
const config = readProjectConfig(appName);
async function readPort(appName: string): Promise<number> {
const config = await readProjectConfig(appName);
return config.targets.serve.options.port;
}
});
+2 -2
View File
@@ -36,7 +36,7 @@ describe('Build React libraries and apps', () => {
let proj: string;
beforeEach(() => {
beforeEach(async () => {
app = uniq('app');
parentLib = uniq('parentlib');
childLib = uniq('childlib');
@@ -99,7 +99,7 @@ describe('Build React libraries and apps', () => {
);
// Add assets to child lib
updateProjectConfig(childLib, (json) => {
await updateProjectConfig(childLib, (json) => {
json.targets.build.options.assets = [`libs/${childLib}/src/assets`];
return json;
});
+2 -2
View File
@@ -234,14 +234,14 @@ describe('React Applications', () => {
${'scss'}
${'less'}
${'styl'}
`('should support global and css modules', ({ style }) => {
`('should support global and css modules', async ({ style }) => {
const appName = uniq('app');
runCLI(
`generate @nx/react:app ${appName} --style=${style} --bundler=webpack --no-interactive`
);
// make sure stylePreprocessorOptions works
updateProjectConfig(appName, (config) => {
await updateProjectConfig(appName, (config) => {
config.targets.build.options.stylePreprocessorOptions = {
includePaths: ['libs/shared/lib'],
};
@@ -17,7 +17,7 @@ describe('React Cypress Component Tests', () => {
const usedInAppLibName = uniq('cy-react-lib');
const buildableLibName = uniq('cy-react-buildable-lib');
beforeAll(() => {
beforeAll(async () => {
projectName = newProject({ name: uniq('cy-react') });
ensureCypressInstallation();
@@ -131,7 +131,7 @@ export default Input;
`
);
createFile('libs/assets/data.json', JSON.stringify({ data: 'data' }));
updateProjectConfig(appName, (config) => {
await updateProjectConfig(appName, (config) => {
config.targets['build'].options.assets.push({
glob: '**/*',
input: 'libs/assets',
@@ -228,7 +228,7 @@ ${content}`;
}
}, 300_000);
it('should work with async webpack config', () => {
it('should work with async webpack config', async () => {
// TODO: (caleb) for whatever reason the MF webpack config + CT is running, but cypress is not starting up?
// are they overriding some option on top of each other causing cypress to not see it's running?
createFile(
@@ -252,7 +252,7 @@ ${content}`;
);
`
);
updateProjectConfig(appName, (config) => {
await updateProjectConfig(appName, (config) => {
config.targets[
'build'
].options.webpackConfig = `apps/${appName}/webpack.config.js`;
+4 -4
View File
@@ -37,7 +37,7 @@ describe('Rollup Plugin', () => {
let output = runCommand(`node dist/libs/${myPkg}/index.cjs.js`);
expect(output).toMatch(/Hello/);
updateProjectConfig(myPkg, (config) => {
await updateProjectConfig(myPkg, (config) => {
delete config.targets.build;
return config;
});
@@ -51,7 +51,7 @@ describe('Rollup Plugin', () => {
output = runCommand(`node dist/libs/${myPkg}/index.cjs.js`);
expect(output).toMatch(/Hello/);
updateProjectConfig(myPkg, (config) => {
await updateProjectConfig(myPkg, (config) => {
delete config.targets.build;
return config;
});
@@ -66,13 +66,13 @@ describe('Rollup Plugin', () => {
expect(output).toMatch(/Hello/);
}, 500000);
it('should support additional entry-points', () => {
it('should support additional entry-points', async () => {
const myPkg = uniq('my-pkg');
runCLI(`generate @nx/js:lib ${myPkg} --bundler=none`);
runCLI(
`generate @nx/rollup:configuration ${myPkg} --target=node --tsConfig=libs/${myPkg}/tsconfig.lib.json --main=libs/${myPkg}/src/index.ts --compiler=tsc`
);
updateProjectConfig(myPkg, (config) => {
await updateProjectConfig(myPkg, (config) => {
config.targets.build.options.format = ['cjs', 'esm'];
config.targets.build.options.generateExportsField = true;
config.targets.build.options.additionalEntryPoints = [
+9 -5
View File
@@ -14,10 +14,11 @@ import { TargetConfiguration } from '@nx/devkit';
import { ChildProcess, exec, execSync, ExecSyncOptions } from 'child_process';
import { join } from 'path';
import * as isCI from 'is-ci';
import { Workspaces } from '../../packages/nx/src/config/workspaces';
import { fileExists, readJson, updateFile } from './file-utils';
import { logError, stripConsoleColors } from './log-utils';
import { existsSync } from 'fs-extra';
import { retrieveProjectConfigurationsSync } from '../../packages/nx/src/project-graph/utils/retrieve-workspace-files';
import { readNxJson } from '../../packages/nx/src/config/nx-json';
export interface RunCmdOpts {
silenceError?: boolean;
@@ -36,11 +37,14 @@ export interface RunCmdOpts {
*/
export function setMaxWorkers() {
if (isCI) {
const ws = new Workspaces(tmpProjPath());
const projectsConfigurations = ws.readProjectsConfigurations();
const root = tmpProjPath();
const projects = retrieveProjectConfigurationsSync(
root,
readNxJson(root)
).projectNodes;
Object.keys(projectsConfigurations.projects).forEach((appName) => {
let project = projectsConfigurations.projects[appName];
Object.keys(projects).forEach((appName) => {
let project = projects[appName];
const { build } = project.targets as {
[targetName: string]: TargetConfiguration<any>;
};
+16 -9
View File
@@ -1,27 +1,34 @@
import { ProjectConfiguration, Workspaces } from '@nx/devkit';
import { ProjectConfiguration } from '@nx/devkit';
import { join } from 'path';
import { tmpProjPath } from './create-project-utils';
import { readJson, updateFile } from './file-utils';
import { retrieveProjectConfigurations } from '../../packages/nx/src/project-graph/utils/retrieve-workspace-files';
import { readNxJson } from '../../packages/nx/src/config/nx-json';
export function updateProjectConfig(
export async function updateProjectConfig(
projectName: string,
callback: (c: ProjectConfiguration) => ProjectConfiguration
) {
const workspace = readResolvedConfiguration();
const root = workspace.projects[projectName].root;
const projects = await readResolvedConfiguration();
const root = projects[projectName].root;
const path = join(root, 'project.json');
const current = readJson(path);
updateFile(path, JSON.stringify(callback(current), null, 2));
}
export function readResolvedConfiguration() {
export async function readResolvedConfiguration(): Promise<
Record<string, ProjectConfiguration>
> {
process.env.NX_PROJECT_GLOB_CACHE = 'false';
const ws = new Workspaces(tmpProjPath());
return ws.readProjectsConfigurations();
const root = tmpProjPath();
return (await retrieveProjectConfigurations(root, readNxJson(root)))
.projectNodes;
}
export function readProjectConfig(projectName: string): ProjectConfiguration {
const root = readResolvedConfiguration().projects[projectName].root;
export async function readProjectConfig(
projectName: string
): Promise<ProjectConfiguration> {
const root = (await readResolvedConfiguration())[projectName].root;
const path = join(root, 'project.json');
return readJson(path);
}
+4 -4
View File
@@ -64,7 +64,7 @@ describe('Vite Plugin', () => {
});
describe('set up new React app with --bundler=vite option', () => {
beforeEach(() => {
beforeEach(async () => {
proj = newProject();
runCLI(`generate @nx/react:app ${myApp} --bundler=vite`);
createFile(`apps/${myApp}/public/hello.md`, `# Hello World`);
@@ -99,7 +99,7 @@ describe('Vite Plugin', () => {
`
);
updateProjectConfig(myApp, (config) => {
await updateProjectConfig(myApp, (config) => {
config.targets.build.options.fileReplacements = [
{
replace: `apps/${myApp}/src/environments/environment.ts`,
@@ -380,7 +380,7 @@ export default App;
}, 100_000);
// TODO: This takes forever and times out everything - find out why
xit('should not delete the project directory when coverage is enabled', () => {
xit('should not delete the project directory when coverage is enabled', async () => {
// when coverage is enabled in the vite.config.ts but reportsDirectory is removed
// from the @nx/vite:test executor options, vite will delete the project root directory
runCLI(`generate @nx/react:lib ${lib} --unitTestRunner=vitest`);
@@ -416,7 +416,7 @@ export default defineConfig({
});
`;
});
updateProjectConfig(lib, (config) => {
await updateProjectConfig(lib, (config) => {
delete config.targets.test.options.reportsDirectory;
return config;
});
+1 -1
View File
@@ -20,7 +20,7 @@ describe('file-server', () => {
const port = 4301;
runCLI(`generate @nx/web:app ${appName} --no-interactive`);
updateProjectConfig(appName, (config) => {
await updateProjectConfig(appName, (config) => {
config.targets['serve'].executor = '@nx/web:file-server';
return config;
});
+5 -5
View File
@@ -262,7 +262,7 @@ describe('Web Components Applications', () => {
`generate @nx/web:app ${appName} --bundler=webpack --no-interactive`
);
updateProjectConfig(appName, (config) => {
await updateProjectConfig(appName, (config) => {
config.targets.build.options.webpackConfig = `apps/${appName}/webpack.config.js`;
return config;
});
@@ -411,7 +411,7 @@ describe('CLI - Environment Variables', () => {
});
describe('Build Options', () => {
it('should inject/bundle external scripts and styles', () => {
it('should inject/bundle external scripts and styles', async () => {
newProject();
const appName = uniq('app');
@@ -444,7 +444,7 @@ describe('Build Options', () => {
const barScriptsBundleName = 'bar-scripts';
const barStylesBundleName = 'bar-styles';
updateProjectConfig(appName, (config) => {
await updateProjectConfig(appName, (config) => {
const buildOptions = config.targets.build.options;
buildOptions.scripts = [
@@ -492,7 +492,7 @@ describe('Build Options', () => {
});
describe('index.html interpolation', () => {
test('should interpolate environment variables', () => {
test('should interpolate environment variables', async () => {
const appName = uniq('app');
runCLI(
@@ -530,7 +530,7 @@ describe('index.html interpolation', () => {
updateFile(envFilePath, envFileContents);
updateFile(indexPath, indexContent);
updateProjectConfig(appName, (config) => {
await updateProjectConfig(appName, (config) => {
const buildOptions = config.targets.build.options;
buildOptions.deployUrl = 'baz';
return config;
+2 -2
View File
@@ -45,7 +45,7 @@ module.exports = composePlugins(withNx(), (config) => {
expect(output).not.toMatch(/Conflicting/);
expect(output).not.toMatch(/process.env.NODE_ENV/);
updateProjectConfig(myPkg, (config) => {
await updateProjectConfig(myPkg, (config) => {
delete config.targets.build;
return config;
});
@@ -59,7 +59,7 @@ module.exports = composePlugins(withNx(), (config) => {
output = runCommand(`node dist/libs/${myPkg}/main.js`);
expect(output).toMatch(/Hello/);
updateProjectConfig(myPkg, (config) => {
await updateProjectConfig(myPkg, (config) => {
delete config.targets.build;
return config;
});
@@ -450,11 +450,13 @@ describe('create-nx-workspace parent folder', () => {
describe('create-nx-workspace yarn berry', () => {
const tmpDir = `${e2eCwd}/${uniq('yarn-berry')}`;
let wsName: string;
let yarnVersion: string;
beforeAll(() => {
mkdirSync(tmpDir, { recursive: true });
runCommand('corepack prepare yarn@stable --activate', { cwd: tmpDir });
runCommand('yarn set version stable', { cwd: tmpDir });
runCommand('corepack prepare yarn@3.6.1 --activate', { cwd: tmpDir });
runCommand('yarn set version 3.6.1', { cwd: tmpDir });
yarnVersion = runCommand('yarn --version', { cwd: tmpDir }).trim();
// previous command creates a package.json file which we don't want
rmSync(`${tmpDir}/package.json`);
process.env.YARN_ENABLE_IMMUTABLE_INSTALLS = 'false';
@@ -477,7 +479,7 @@ describe('create-nx-workspace yarn berry', () => {
).toMatchInlineSnapshot(`
"nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-3.6.1.cjs
yarnPath: .yarn/releases/yarn-${yarnVersion}.cjs
"
`);
});
@@ -497,7 +499,7 @@ describe('create-nx-workspace yarn berry', () => {
).toMatchInlineSnapshot(`
"nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-3.6.1.cjs
yarnPath: .yarn/releases/yarn-${yarnVersion}.cjs
"
`);
});
+1 -1
View File
@@ -1,6 +1,6 @@
{
"packages": ["build/packages/*", "build/packages/nx/native-packages/*"],
"version": "16.7.0",
"version": "16.7.2",
"granularPathspec": false,
"command": {
"publish": {
@@ -38,17 +38,15 @@ export function getPackageManagerCommand(
exec: string;
preInstall?: string;
} {
const [pmMajor, pmMinor] =
getPackageManagerVersion(packageManager).split('.');
const pmVersion = getPackageManagerVersion(packageManager);
const [pmMajor, pmMinor] = pmVersion.split('.');
switch (packageManager) {
case 'yarn':
const useBerry = +pmMajor >= 2;
const installCommand = 'yarn install --silent';
return {
preInstall: useBerry
? 'yarn set version stable'
: 'yarn set version classic',
preInstall: `yarn set version ${pmVersion}`,
install: useBerry
? installCommand
: `${installCommand} --ignore-scripts`,
+1 -4
View File
@@ -47,10 +47,7 @@ export type {
*/
export { Workspaces } from 'nx/src/config/workspaces';
export {
readAllWorkspaceConfiguration,
workspaceLayout,
} from 'nx/src/config/configuration';
export { workspaceLayout } from 'nx/src/config/configuration';
export type {
NxPlugin,
@@ -1,10 +1,11 @@
import type { Target } from 'nx/src/command-line/run/run';
import type { ProjectGraph } from 'nx/src/config/project-graph';
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
import { splitTarget } from 'nx/src/utils/split-target';
import { requireNx } from '../../nx';
const { readCachedProjectGraph } = requireNx();
let { readCachedProjectGraph, splitTarget } = requireNx();
// TODO: Remove this in Nx 18 when Nx 16.7.0 is no longer supported
splitTarget = splitTarget ?? require('nx/src/utils/split-target').splitTarget;
/**
* @deprecated(v17) A project graph should be passed to parseTargetString for best accuracy.
@@ -1,12 +1,19 @@
import type { Target } from 'nx/src/command-line/run/run';
import type { ExecutorContext } from 'nx/src/config/misc-interfaces';
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
import { combineOptionsForExecutor } from 'nx/src/utils/params';
import { requireNx } from '../../nx';
import { relative } from 'path';
const { Workspaces, getExecutorInformation, calculateDefaultProjectName } =
requireNx();
let {
Workspaces,
getExecutorInformation,
calculateDefaultProjectName,
combineOptionsForExecutor,
} = requireNx();
// TODO: Remove this in Nx 18 when Nx 16.7.0 is no longer supported
combineOptionsForExecutor =
combineOptionsForExecutor ??
require('nx/src/utils/params').combineOptionsForExecutor;
/**
* Reads and combines options for a given target.
@@ -1,11 +1,13 @@
import type { Tree } from 'nx/src/generators/tree';
import * as path from 'path';
import type * as Prettier from 'prettier';
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
import { sortObjectByKeys } from 'nx/src/utils/object-sort';
import { requireNx } from '../../nx';
const { updateJson, readJson } = requireNx();
let { updateJson, readJson, sortObjectByKeys } = requireNx();
// TODO: Remove this in Nx 18 when Nx 16.7.0 is no longer supported
sortObjectByKeys =
sortObjectByKeys ?? require('nx/src/utils/object-sort').sortObjectByKeys;
/**
* Formats all the created or updated files using Prettier
@@ -1,8 +1,14 @@
import type { Observable } from 'rxjs';
import type { Executor, ExecutorContext } from 'nx/src/config/misc-interfaces';
import type { ProjectsConfigurations } from 'nx/src/devkit-exports';
import { requireNx } from '../../nx';
const { Workspaces, readNxJsonFromDisk } = requireNx();
const {
Workspaces,
readNxJsonFromDisk,
retrieveProjectConfigurationsWithAngularProjects,
} = requireNx();
/**
* Convert an Nx Executor into an Angular Devkit Builder
@@ -17,11 +23,22 @@ export function convertNxExecutor(executor: Executor) {
? readNxJsonFromDisk(builderContext.workspaceRoot)
: // TODO(v18): remove readNxJson. This is to be backwards compatible with Nx 16.5 and below.
(workspaces as any).readNxJson();
const projectsConfigurations = workspaces.readProjectsConfigurations({
_includeProjectsFromAngularJson: true,
});
const promise = async () => {
const projectsConfigurations: ProjectsConfigurations =
retrieveProjectConfigurationsWithAngularProjects
? {
version: 2,
projects: await retrieveProjectConfigurationsWithAngularProjects(
builderContext.workspaceRoot,
nxJsonConfiguration
).then((p) => p.projectNodes),
}
: // TODO(v18): remove retrieveProjectConfigurations. This is to be backwards compatible with Nx 16.5 and below.
(workspaces as any).readProjectsConfigurations({
_includeProjectsFromAngularJson: true,
});
const context: ExecutorContext = {
root: builderContext.workspaceRoot,
projectName: builderContext.target.project,
@@ -1,5 +1,4 @@
// eslint-disable-next-line @nx/enforce-module-boundaries
import { createTreeWithEmptyWorkspace } from 'nx/src/generators/testing-utils/create-tree-with-empty-workspace';
import { createTreeWithEmptyWorkspace } from 'nx/src/devkit-testing-exports';
import { getWorkspaceLayout } from './get-workspace-layout';
describe('getWorkspaceLayout', () => {
@@ -1,5 +1,3 @@
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
import { stripIndent } from 'nx/src/utils/logger';
import type {
FileChange,
Tree,
@@ -13,7 +11,10 @@ import { join, relative } from 'path';
import type { Mode } from 'fs';
import { requireNx } from '../../nx';
const { logger } = requireNx();
let { logger, stripIndent } = requireNx();
// TODO: Remove this in Nx 18 when Nx 16.7.0 is no longer supported
stripIndent = stripIndent ?? require('nx/src/utils/logger').stripIndent;
class RunCallbackTask {
constructor(private callback: GeneratorCallback) {}
@@ -3,11 +3,16 @@ import { WorkspaceLibrarySecondaryEntryPoint } from './models';
import { dirname, join, relative } from 'path';
import { existsSync, lstatSync, readdirSync } from 'fs';
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
import { PackageJson, readModulePackageJson } from 'nx/src/utils/package-json';
import type { PackageJson } from 'nx/src/utils/package-json';
import { requireNx } from '../../../nx';
const { readJsonFile, joinPathFragments, workspaceRoot } = requireNx();
let { readJsonFile, joinPathFragments, workspaceRoot, readModulePackageJson } =
requireNx();
// TODO: Remove this in Nx 18 when Nx 16.7.0 is no longer supported
readModulePackageJson =
readModulePackageJson ??
require('nx/src/utils/package-json').readModulePackageJson;
export function collectWorkspaceLibrarySecondaryEntryPoints(
library: WorkspaceLibrary,
@@ -9,6 +9,7 @@ import {
DepConstraint,
findConstraintsFor,
findTransitiveExternalDependencies,
getSourceFilePath,
hasBannedDependencies,
hasBannedImport,
hasNoneOfTheseTags,
@@ -558,3 +559,19 @@ describe('hasNoneOfTheseTags', () => {
}
);
});
describe('getSourceFilePath', () => {
it.each([
['/root/libs/dev-kit/package.json', '/root'],
['/root/libs/dev-kit/package.json', 'C:\\root'],
['C:\\root\\libs\\dev-kit\\package.json', '/root'],
['C:\\root\\libs\\dev-kit\\package.json', 'C:\\root'],
])(
'should return "libs/dev-kit/package.json" when sourceFileName is "%s" and projectPath is "%s"',
(sourceFileName, projectPath) => {
expect(getSourceFilePath(sourceFileName, projectPath)).toBe(
'libs/dev-kit/package.json'
);
}
);
});
@@ -222,8 +222,9 @@ export function onlyLoadChildren(
}
export function getSourceFilePath(sourceFileName: string, projectPath: string) {
const relativePath = sourceFileName.slice(projectPath.length + 1);
return normalizePath(relativePath);
const normalizedProjectPath = normalizePath(projectPath);
const normalizedSourceFileName = normalizePath(sourceFileName);
return normalizedSourceFileName.slice(normalizedProjectPath.length + 1);
}
/**
@@ -743,4 +743,4 @@ function determineEntryFields(
}
export default libraryGenerator;
export const librarySchematic = convertNxGenerator(libraryGeneratorInternal);
export const librarySchematic = convertNxGenerator(libraryGenerator);
+51 -26
View File
@@ -1,15 +1,13 @@
import 'dotenv/config';
import {
detectPackageManager,
ExecutorContext,
getPackageManagerVersion,
logger,
readJsonFile,
workspaceRoot,
writeJsonFile,
} from '@nx/devkit';
import { createLockFile, createPackageJson, getLockFileName } from '@nx/js';
import { join } from 'path';
import { join, resolve as pathResolve } from 'path';
import { copySync, existsSync, mkdir, writeFileSync } from 'fs-extra';
import { gte } from 'semver';
import { directoryExists } from '@nx/workspace/src/utilities/fileutils';
@@ -19,9 +17,11 @@ import { updatePackageJson } from './lib/update-package-json';
import { createNextConfigFile } from './lib/create-next-config-file';
import { checkPublicDirectory } from './lib/check-project';
import { NextBuildBuilderOptions } from '../../utils/types';
import { execSync, ExecSyncOptions } from 'child_process';
import { ChildProcess, fork } from 'child_process';
import { createCliOptions } from '../../utils/create-cli-options';
let childProcess: ChildProcess;
export default async function buildExecutor(
options: NextBuildBuilderOptions,
context: ExecutorContext
@@ -49,32 +49,16 @@ export default async function buildExecutor(
process.env['__NEXT_REACT_ROOT'] ||= 'true';
}
const { experimentalAppOnly, profile, debug, outputPath } = options;
// Set output path here since it can also be set via CLI
// We can retrieve it inside plugins/with-nx
process.env.NX_NEXT_OUTPUT_PATH ??= outputPath;
const args = createCliOptions({ experimentalAppOnly, profile, debug });
const isYarnBerry =
detectPackageManager() === 'yarn' &&
gte(getPackageManagerVersion('yarn', workspaceRoot), '2.0.0');
const buildCommand = isYarnBerry
? `yarn next build ${projectRoot}`
: 'npx next build';
const command = `${buildCommand} ${args.join(' ')}`;
const execSyncOptions: ExecSyncOptions = {
stdio: 'inherit',
encoding: 'utf-8',
cwd: projectRoot,
};
try {
execSync(command, execSyncOptions);
await runCliBuild(workspaceRoot, projectRoot, options);
} catch (error) {
logger.error(`Error occurred while trying to run the ${command}`);
logger.error(`Error occurred while trying to run the build command`);
logger.error(error);
return { success: false };
} finally {
if (childProcess) {
childProcess.kill();
}
}
if (!directoryExists(options.outputPath)) {
@@ -116,3 +100,44 @@ export default async function buildExecutor(
}
return { success: true };
}
function runCliBuild(
workspaceRoot: string,
projectRoot: string,
options: NextBuildBuilderOptions
) {
const { experimentalAppOnly, profile, debug, outputPath } = options;
// Set output path here since it can also be set via CLI
// We can retrieve it inside plugins/with-nx
process.env.NX_NEXT_OUTPUT_PATH ??= outputPath;
const args = createCliOptions({ experimentalAppOnly, profile, debug });
return new Promise((resolve, reject) => {
childProcess = fork(
require.resolve('next/dist/bin/next'),
['build', ...args],
{
cwd: pathResolve(workspaceRoot, projectRoot),
stdio: 'inherit',
env: process.env,
}
);
// Ensure the child process is killed when the parent exits
process.on('exit', () => childProcess.kill());
process.on('SIGTERM', () => childProcess.kill());
childProcess.on('error', (err) => {
reject(err);
});
childProcess.on('exit', (code) => {
if (code === 0) {
resolve(code);
} else {
reject(code);
}
});
});
}
+1
View File
@@ -14,6 +14,7 @@ ignore = '0.4'
ignore-files = "1.3.0"
itertools = "0.10.5"
once_cell = "1.18.0"
os_type = "2.6.0"
napi = { version = '2.12.6', default-features = false, features = ['anyhow', 'napi4', 'tokio_rt'] }
napi-derive = '2.9.3'
regex = "1.9.1"
@@ -1,7 +1,7 @@
import * as memfs from 'memfs';
import '../src/utils/testing/mock-fs';
import { getNxPackageJsonWorkspacesPlugin } from './package-json-workspaces';
import { createNodeFromPackageJson } from './package-json-workspaces';
describe('nx package.json workspaces plugin', () => {
it('should build projects from package.json files', () => {
@@ -15,21 +15,29 @@ describe('nx package.json workspaces plugin', () => {
name: 'lib-a',
scripts: { test: 'jest' },
}),
'packages/lib-b/package.json': JSON.stringify({
name: 'lib-b',
scripts: {
build: 'tsc',
test: 'jest',
nonNxOperation: 'rm -rf .',
},
nx: {
implicitDependencies: ['lib-a'],
includedScripts: ['build', 'test'],
targets: {
build: {
outputs: ['{projectRoot}/dist'],
},
},
},
}),
},
'/root'
);
const plugin = getNxPackageJsonWorkspacesPlugin('/root');
// Targets from package.json files are handled outside of `createNodes`,
// because they are recognized even if the package.json file is not included
// in the package manager workspaces configuration.
//
// If any project has a package.json file in its root directory, those scripts
// are targets regardless of this plugin. As such, all we have to do here is identify
// that the package.json represents an Nx project, and `normalizeProjectNodes`
// will handle the rest.
expect(plugin.createNodes[1]('package.json', null)).toMatchInlineSnapshot(`
expect(createNodeFromPackageJson('package.json', '/root'))
.toMatchInlineSnapshot(`
{
"projects": {
"root": {
@@ -37,11 +45,19 @@ describe('nx package.json workspaces plugin', () => {
"projectType": "library",
"root": ".",
"sourceRoot": ".",
"targets": {
"echo": {
"executor": "nx:run-script",
"options": {
"script": "echo",
},
},
},
},
},
}
`);
expect(plugin.createNodes[1]('packages/lib-a/package.json', null))
expect(createNodeFromPackageJson('packages/lib-a/package.json', '/root'))
.toMatchInlineSnapshot(`
{
"projects": {
@@ -50,6 +66,51 @@ describe('nx package.json workspaces plugin', () => {
"projectType": "library",
"root": "packages/lib-a",
"sourceRoot": "packages/lib-a",
"targets": {
"test": {
"executor": "nx:run-script",
"options": {
"script": "test",
},
},
},
},
},
}
`);
expect(createNodeFromPackageJson('packages/lib-b/package.json', '/root'))
.toMatchInlineSnapshot(`
{
"projects": {
"lib-b": {
"implicitDependencies": [
"lib-a",
],
"includedScripts": [
"build",
"test",
],
"name": "lib-b",
"projectType": "library",
"root": "packages/lib-b",
"sourceRoot": "packages/lib-b",
"targets": {
"build": {
"executor": "nx:run-script",
"options": {
"script": "build",
},
"outputs": [
"{projectRoot}/dist",
],
},
"test": {
"executor": "nx:run-script",
"options": {
"script": "test",
},
},
},
},
},
}
+21 -14
View File
@@ -9,7 +9,10 @@ import { combineGlobPatterns } from '../src/utils/globs';
import { NX_PREFIX } from '../src/utils/logger';
import { NxPluginV2 } from '../src/utils/nx-plugin';
import { output } from '../src/utils/output';
import { PackageJson } from '../src/utils/package-json';
import {
PackageJson,
readTargetsFromPackageJson,
} from '../src/utils/package-json';
import { joinPathFragments } from '../src/utils/path';
export function getNxPackageJsonWorkspacesPlugin(root: string): NxPluginV2 {
@@ -20,24 +23,26 @@ export function getNxPackageJsonWorkspacesPlugin(root: string): NxPluginV2 {
combineGlobPatterns(
getGlobPatternsFromPackageManagerWorkspaces(root, readJson)
),
(pkgJsonPath) => {
const json: PackageJson = readJson(pkgJsonPath);
return {
projects: {
[json.name]: buildProjectConfigurationFromPackageJson(
json,
pkgJsonPath,
readNxJson(root)
),
},
};
},
(p) => createNodeFromPackageJson(p, root),
],
};
}
export function createNodeFromPackageJson(pkgJsonPath: string, root: string) {
const json: PackageJson = readJsonFile(join(root, pkgJsonPath));
return {
projects: {
[json.name]: buildProjectConfigurationFromPackageJson(
json,
pkgJsonPath,
readNxJson(root)
),
},
};
}
export function buildProjectConfigurationFromPackageJson(
packageJson: { name: string },
packageJson: PackageJson,
path: string,
nxJson: NxJsonConfiguration
): ProjectConfiguration & { name: string } {
@@ -69,6 +74,8 @@ export function buildProjectConfigurationFromPackageJson(
sourceRoot: directory,
name,
projectType,
...packageJson.nx,
targets: readTargetsFromPackageJson(packageJson),
};
}
+13 -1
View File
@@ -1 +1,13 @@
export const projectFilePatterns = ['package.json'];
import type { NxPluginV2 } from '../src/utils/nx-plugin';
import { workspaceRoot } from '../src/utils/workspace-root';
import { createNodeFromPackageJson } from './package-json-workspaces';
const plugin: NxPluginV2 = {
name: 'nx-all-package-jsons-plugin',
createNodes: [
'*/**/package.json',
(f) => createNodeFromPackageJson(f, workspaceRoot),
],
};
module.exports = plugin;
+180 -5
View File
@@ -1,7 +1,13 @@
import * as memfs from 'memfs';
import '../src/utils/testing/mock-fs';
import { getNxProjectJsonPlugin } from './project-json';
import { PackageJson } from '../src/utils/package-json';
import {
getNxProjectJsonPlugin,
mergeNpmScriptsWithTargets,
} from './project-json';
describe('nx project.json plugin', () => {
it('should build projects from project.json', () => {
@@ -14,8 +20,17 @@ describe('nx project.json plugin', () => {
'packages/lib-a/project.json': JSON.stringify({
name: 'lib-a',
targets: {
executor: 'nx:run-commands',
options: {},
build: {
executor: 'nx:run-commands',
options: {},
},
},
}),
'packages/lib-a/package.json': JSON.stringify({
name: 'lib-a',
scripts: {
build: 'should not see me',
test: 'jest',
},
}),
},
@@ -44,12 +59,172 @@ describe('nx project.json plugin', () => {
"name": "lib-a",
"root": "packages/lib-a",
"targets": {
"executor": "nx:run-commands",
"options": {},
"build": {
"executor": "nx:run-commands",
"options": {},
},
"test": {
"executor": "nx:run-script",
"options": {
"script": "test",
},
},
},
},
},
}
`);
});
describe('mergeNpmScriptsWithTargets', () => {
const packageJson: PackageJson = {
name: 'my-app',
version: '0.0.0',
scripts: {
build: 'echo 1',
},
};
const packageJsonBuildTarget = {
executor: 'nx:run-script',
options: {
script: 'build',
},
};
it('should prefer project.json targets', () => {
const projectJsonTargets = {
build: {
executor: 'nx:run-commands',
options: {
command: 'echo 2',
},
},
};
const result = mergeNpmScriptsWithTargets(
packageJson,
projectJsonTargets
);
expect(result).toEqual(projectJsonTargets);
});
it('should provide targets from project.json and package.json', () => {
const projectJsonTargets = {
clean: {
executor: 'nx:run-commands',
options: {
command: 'echo 2',
},
},
};
const result = mergeNpmScriptsWithTargets(
packageJson,
projectJsonTargets
);
expect(result).toEqual({
...projectJsonTargets,
build: packageJsonBuildTarget,
});
});
it('should contain extended options from nx property in package.json', () => {
const result = mergeNpmScriptsWithTargets(
{
name: 'my-other-app',
version: '',
scripts: {
build: 'echo 1',
},
nx: {
targets: {
build: {
outputs: ['custom'],
},
},
},
},
null
);
expect(result).toEqual({
build: { ...packageJsonBuildTarget, outputs: ['custom'] },
});
});
it('should work when project.json targets is null', () => {
const result = mergeNpmScriptsWithTargets(packageJson, null);
expect(result).toEqual({
build: {
executor: 'nx:run-script',
options: {
script: 'build',
},
},
});
});
it("should work when project root is ''", () => {
const result = mergeNpmScriptsWithTargets(
{
name: 'my-app',
version: '',
scripts: {
test: 'echo testing',
},
},
{
build: {
executor: 'nx:run-commands',
options: { command: 'echo hi' },
},
}
);
expect(result).toEqual({
build: {
executor: 'nx:run-commands',
options: { command: 'echo hi' },
},
test: {
executor: 'nx:run-script',
options: { script: 'test' },
},
});
});
it('should ignore scripts that are not in includedScripts', () => {
const result = mergeNpmScriptsWithTargets(
{
name: 'included-scripts-test',
version: '',
scripts: {
test: 'echo testing',
fail: 'exit 1',
},
nx: {
includedScripts: ['test'],
},
},
{
build: {
executor: 'nx:run-commands',
options: { command: 'echo hi' },
},
}
);
expect(result).toEqual({
build: {
executor: 'nx:run-commands',
options: { command: 'echo hi' },
},
test: {
executor: 'nx:run-script',
options: { script: 'test' },
},
});
});
});
});
+53 -1
View File
@@ -1,9 +1,17 @@
import { dirname, join } from 'node:path';
import { existsSync } from 'node:fs';
import { ProjectConfiguration } from '../src/config/workspace-json-project-json';
import {
ProjectConfiguration,
TargetConfiguration,
} from '../src/config/workspace-json-project-json';
import { toProjectName } from '../src/config/workspaces';
import { readJsonFile } from '../src/utils/fileutils';
import { NxPluginV2 } from '../src/utils/nx-plugin';
import {
PackageJson,
readTargetsFromPackageJson,
} from '../src/utils/package-json';
export function getNxProjectJsonPlugin(root: string): NxPluginV2 {
return {
@@ -13,6 +21,7 @@ export function getNxProjectJsonPlugin(root: string): NxPluginV2 {
(file) => {
const json = readJsonFile<ProjectConfiguration>(join(root, file));
const project = buildProjectFromProjectJson(json, file);
mergePackageJsonConfigurationWithProjectJson(project, root);
return {
projects: {
[project.name]: project,
@@ -33,3 +42,46 @@ export function buildProjectFromProjectJson(
...json,
};
}
export function mergePackageJsonConfigurationWithProjectJson(
p: ProjectConfiguration,
root: string
) {
if (existsSync(join(root, p.root, 'package.json'))) {
try {
const packageJson: PackageJson = readJsonFile(
join(root, p.root, 'package.json')
);
p.targets = mergeNpmScriptsWithTargets(packageJson, p.targets);
const { nx } = packageJson;
if (nx?.tags) {
p.tags = [...(p.tags || []), ...nx.tags];
}
if (nx?.implicitDependencies) {
p.implicitDependencies = [
...(p.implicitDependencies || []),
...nx.implicitDependencies,
];
}
if (nx?.namedInputs) {
p.namedInputs = { ...(p.namedInputs || {}), ...nx.namedInputs };
}
} catch (e) {
// ignore json parser errors
}
}
}
export function mergeNpmScriptsWithTargets(
packageJson: PackageJson,
targets: Record<string, TargetConfiguration>
): Record<string, TargetConfiguration> {
try {
return { ...readTargetsFromPackageJson(packageJson), ...(targets || {}) };
} catch (e) {
return targets;
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "https://nx.dev/core-concepts/configuration#nxjson",
"$id": "https://nx.dev/reference/nx-json",
"title": "JSON schema for Nx configuration",
"type": "object",
"properties": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "https://nx.dev/project-schema",
"$id": "https://nx.dev/reference/project-configuration",
"title": "JSON schema for Nx projects",
"type": "object",
"properties": {
+14 -23
View File
@@ -1,10 +1,20 @@
import { existsSync } from 'fs';
import * as path from 'path';
import { readJsonFile } from '../utils/fileutils';
import {
ProjectConfiguration,
ProjectsConfigurations,
} from '../config/workspace-json-project-json';
import { ProjectsConfigurations } from '../config/workspace-json-project-json';
import { NxPluginV2 } from '../devkit-exports';
export const NX_ANGULAR_JSON_PLUGIN_NAME = 'nx-angular-json-plugin';
export const NxAngularJsonPlugin: NxPluginV2 = {
name: NX_ANGULAR_JSON_PLUGIN_NAME,
createNodes: [
'angular.json',
(f, ctx) => ({
projects: readAngularJson(ctx.workspaceRoot),
}),
],
};
export function shouldMergeAngularProjects(
root: string,
@@ -45,25 +55,6 @@ function readAngularJson(angularCliWorkspaceRoot: string) {
).projects;
}
export function mergeAngularJsonAndProjects(
projects: {
[name: string]: ProjectConfiguration;
},
angularCliWorkspaceRoot: string
): { [name: string]: ProjectConfiguration } {
const res = readAngularJson(angularCliWorkspaceRoot);
const folders = new Set();
for (let k of Object.keys(res)) {
folders.add(res[k].root);
}
for (let k of Object.keys(projects)) {
if (!folders.has(projects[k].root)) {
res[k] = projects[k];
}
}
return res;
}
export function toNewFormat(w: any): ProjectsConfigurations {
Object.values(w.projects || {}).forEach((projectConfig: any) => {
if (projectConfig.architect) {
-1
View File
@@ -1,4 +1,3 @@
import { logger } from '../utils/logger';
import {
createProjectGraphAsync,
readProjectsConfigurationFromProjectGraph,
@@ -44,7 +44,7 @@ export function calculateDefaultProjectName(
}
export function findMatchingProjectInCwd(
projects: { [projectName: string]: ProjectConfiguration },
projects: Record<string, ProjectConfiguration>,
relativeCwd: string
): string | undefined {
const projectRootMappings = new Map<string, string>();
-13
View File
@@ -1,18 +1,5 @@
import { Workspaces } from './workspaces';
import { workspaceRoot } from '../utils/workspace-root';
import { NxJsonConfiguration } from './nx-json';
import { ProjectsConfigurations } from './workspace-json-project-json';
import { readNxJson } from './nx-json';
// TODO(vsavkin): Remove after Nx 16 is out
/**
* @deprecated Use readProjectsConfigurationFromProjectGraph(await createProjectGraphAsync())
*/
export function readAllWorkspaceConfiguration(): ProjectsConfigurations &
NxJsonConfiguration {
return new Workspaces(workspaceRoot).readProjectsConfigurations();
}
/**
* Returns information about where apps and libs will be created.
*/
+14 -6
View File
@@ -1,12 +1,15 @@
import { toProjectName, Workspaces } from './workspaces';
import { TempFs } from '../utils/testing/temp-fs';
import { withEnvironmentVariables } from '../../internal-testing-utils/with-environment';
import { retrieveProjectConfigurations } from '../project-graph/utils/retrieve-workspace-files';
import { readNxJson } from './configuration';
const libConfig = (root, name?: string) => ({
name: name ?? toProjectName(`${root}/some-file`),
projectType: 'library',
root: `libs/${root}`,
sourceRoot: `libs/${root}/src`,
targets: {},
});
const packageLibConfig = (root, name?: string) => ({
@@ -14,6 +17,7 @@ const packageLibConfig = (root, name?: string) => ({
root,
sourceRoot: root,
projectType: 'library',
targets: {},
});
describe('Workspaces', () => {
@@ -45,7 +49,7 @@ describe('Workspaces', () => {
});
const workspaces = new Workspaces(fs.tempDir);
const resolved = workspaces.readProjectsConfigurations();
const resolved = workspaces.readWorkspaceConfiguration();
expect(resolved.projects.lib1).toEqual(standaloneConfig);
});
@@ -74,7 +78,7 @@ describe('Workspaces', () => {
});
const workspaces = new Workspaces(fs.tempDir);
const { projects } = workspaces.readProjectsConfigurations();
const { projects } = workspaces.readWorkspaceConfiguration();
// projects got merged for lib1
expect(projects['lib1']).toEqual({
@@ -82,6 +86,7 @@ describe('Workspaces', () => {
root: 'libs/lib1',
sourceRoot: 'libs/lib1/src',
projectType: 'library',
targets: {},
});
expect(projects.lib2).toEqual(lib2Config);
expect(projects['domain-lib3']).toEqual(domainPackageConfig);
@@ -112,14 +117,17 @@ describe('Workspaces', () => {
{
NX_WORKSPACE_ROOT: fs.tempDir,
},
() => {
const workspaces = new Workspaces(fs.tempDir);
const resolved = workspaces.readProjectsConfigurations();
expect(resolved.projects['my-package']).toEqual({
async () => {
const resolved = await retrieveProjectConfigurations(
fs.tempDir,
readNxJson(fs.tempDir)
);
expect(resolved.projectNodes['my-package']).toEqual({
name: 'my-package',
root: 'packages/my-package',
sourceRoot: 'packages/my-package',
projectType: 'library',
targets: {},
});
}
);
+14 -95
View File
@@ -1,110 +1,29 @@
import { dirname, join } from 'path';
import { workspaceRoot } from '../utils/workspace-root';
import { readJsonFile } from '../utils/fileutils';
import { getNxRequirePaths } from '../utils/installation-directory';
import { loadNxPlugins, loadNxPluginsSync } from '../utils/nx-plugin';
import { dirname } from 'path';
import type { NxJsonConfiguration } from './nx-json';
import { readNxJson } from './nx-json';
import {
ProjectConfiguration,
ProjectsConfigurations,
} from './workspace-json-project-json';
import {
mergeAngularJsonAndProjects,
shouldMergeAngularProjects,
} from '../adapter/angular-json';
import { retrieveProjectConfigurationPaths } from '../project-graph/utils/retrieve-workspace-files';
import {
buildProjectsConfigurationsFromProjectPathsAndPlugins,
mergeTargetConfigurations,
readTargetDefaultsForTarget,
} from '../project-graph/utils/project-configuration-utils';
import { ProjectsConfigurations } from './workspace-json-project-json';
import { retrieveProjectConfigurationsSync } from '../project-graph/utils/retrieve-workspace-files';
// TODO(v18): remove this class
/**
* @deprecated This will be removed in v18. Use {@link readProjectsConfigurationFromProjectGraph} instead.
*/
export class Workspaces {
private cachedProjectsConfig: ProjectsConfigurations;
constructor(private root: string) {}
/**
* @deprecated
* @deprecated Use {@link readProjectsConfigurationFromProjectGraph} instead.
*/
readProjectsConfigurations(opts?: {
_includeProjectsFromAngularJson?: boolean;
}): ProjectsConfigurations {
if (
this.cachedProjectsConfig &&
process.env.NX_CACHE_PROJECTS_CONFIG !== 'false'
) {
return this.cachedProjectsConfig;
}
readWorkspaceConfiguration(): ProjectsConfigurations & NxJsonConfiguration {
const nxJson = readNxJson(this.root);
const projectPaths = retrieveProjectConfigurationPaths(this.root, nxJson);
let projectsConfigurations =
buildProjectsConfigurationsFromProjectPathsAndPlugins(
nxJson,
projectPaths,
loadNxPluginsSync(
nxJson.plugins,
getNxRequirePaths(this.root),
this.root
),
this.root
).projects;
if (
shouldMergeAngularProjects(
this.root,
opts?._includeProjectsFromAngularJson
)
) {
projectsConfigurations = mergeAngularJsonAndProjects(
projectsConfigurations,
this.root
);
}
this.cachedProjectsConfig = {
return {
version: 2,
projects: projectsConfigurations,
projects: retrieveProjectConfigurationsSync(this.root, nxJson)
.projectNodes,
...nxJson,
};
return this.cachedProjectsConfig;
}
/**
* Deprecated. Use readProjectsConfigurations
*/
readWorkspaceConfiguration(opts?: {
_ignorePluginInference?: boolean;
_includeProjectsFromAngularJson?: boolean;
}): ProjectsConfigurations & NxJsonConfiguration {
const nxJson = readNxJson(this.root);
return { ...this.readProjectsConfigurations(opts), ...nxJson };
}
private mergeTargetDefaultsIntoProjectDescriptions(
projects: Record<string, ProjectConfiguration>,
nxJson: NxJsonConfiguration
) {
for (const proj of Object.values(projects)) {
if (proj.targets) {
for (const targetName of Object.keys(proj.targets ?? {})) {
const projectTargetDefinition = proj.targets[targetName];
const defaults = readTargetDefaultsForTarget(
targetName,
nxJson.targetDefaults,
projectTargetDefinition.executor
);
if (defaults) {
proj.targets[targetName] = mergeTargetConfigurations(
proj,
targetName,
defaults
);
}
}
}
}
return projects;
}
}
@@ -15,7 +15,6 @@ import {
import { fileExists } from '../../utils/fileutils';
import { notifyFileWatcherSockets } from './file-watching/file-watcher-sockets';
import { serverLogger } from './logger';
import { Workspaces } from '../../config/workspaces';
import { workspaceRoot } from '../../utils/workspace-root';
import { execSync } from 'child_process';
import { fileHasher, hashArray } from '../../hasher/file-hasher';
@@ -23,7 +22,10 @@ import {
retrieveWorkspaceFiles,
retrieveProjectConfigurations,
} from '../../project-graph/utils/retrieve-workspace-files';
import { ProjectConfiguration } from '../../config/workspace-json-project-json';
import {
ProjectConfiguration,
ProjectsConfigurations,
} from '../../config/workspace-json-project-json';
import { readNxJson } from '../../config/nx-json';
let cachedSerializedProjectGraphPromise: Promise<{
@@ -173,7 +175,7 @@ async function processCollectedUpdatedAndDeletedFiles() {
);
fileHasher.incrementalUpdate(updatedFiles, deletedFiles);
let nxJson = readNxJson(workspaceRoot);
const nxJson = readNxJson(workspaceRoot);
const { projectNodes } = await retrieveProjectConfigurations(
workspaceRoot,
@@ -264,16 +266,17 @@ async function createAndSerializeProjectGraph(): Promise<{
}> {
try {
performance.mark('create-project-graph-start');
const projectsConfigurations = new Workspaces(
workspaceRoot
).readProjectsConfigurations();
const projectConfigurations = await retrieveProjectConfigurations(
workspaceRoot,
readNxJson(workspaceRoot)
);
const projectFileMap = copyFileMap(projectFileMapWithFiles.projectFileMap);
const allWorkspaceFiles = copyFileData(
projectFileMapWithFiles.allWorkspaceFiles
);
const { projectGraph, projectFileMapCache } =
await buildProjectGraphUsingProjectFileMap(
projectsConfigurations,
projectConfigurations.projectNodes,
knownExternalNodes,
projectFileMap,
allWorkspaceFiles,
+3 -4
View File
@@ -37,15 +37,13 @@ export type {
HasherContext,
} from './config/misc-interfaces';
// TODO(v18): Remove this export
/**
* @category Workspace
*/
export { Workspaces } from './config/workspaces';
export {
readAllWorkspaceConfiguration,
workspaceLayout,
} from './config/configuration';
export { workspaceLayout } from './config/configuration';
export type {
NxPlugin,
@@ -208,6 +206,7 @@ export { reverse } from './project-graph/operators';
export {
createProjectGraphAsync,
readCachedProjectGraph,
readProjectsConfigurationFromProjectGraph,
} from './project-graph/project-graph';
/**
+6
View File
@@ -7,3 +7,9 @@ export { createTempNpmDirectory } from './utils/package-manager';
export { getExecutorInformation } from './command-line/run/executor-utils';
export { readNxJson as readNxJsonFromDisk } from './config/nx-json';
export { calculateDefaultProjectName } from './config/calculate-default-project-name';
export { retrieveProjectConfigurationsWithAngularProjects } from './project-graph/utils/retrieve-workspace-files';
export { splitTarget } from './utils/split-target';
export { combineOptionsForExecutor } from './utils/params';
export { sortObjectByKeys } from './utils/object-sort';
export { stripIndent } from './utils/logger';
export { readModulePackageJson } from './utils/package-json';
@@ -3,9 +3,10 @@
*/
import type { Observable } from 'rxjs';
import { Workspaces } from '../../config/workspaces';
import { readNxJson } from '../../config/nx-json';
import { Executor, ExecutorContext } from '../../config/misc-interfaces';
import { retrieveProjectConfigurations } from '../../project-graph/utils/retrieve-workspace-files';
import { ProjectsConfigurations } from '../../config/workspace-json-project-json';
/**
* Convert an Nx Executor into an Angular Devkit Builder
@@ -14,11 +15,17 @@ import { Executor, ExecutorContext } from '../../config/misc-interfaces';
*/
export function convertNxExecutor(executor: Executor) {
const builderFunction = (options, builderContext) => {
const workspaces = new Workspaces(builderContext.workspaceRoot);
const projectsConfigurations = workspaces.readProjectsConfigurations();
const promise = async () => {
const nxJsonConfiguration = readNxJson(builderContext.workspaceRoot);
const projectsConfigurations: ProjectsConfigurations = {
version: 2,
projects: (
await retrieveProjectConfigurations(
builderContext.workspaceRoot,
nxJsonConfiguration
)
).projectNodes,
};
const context: ExecutorContext = {
root: builderContext.workspaceRoot,
projectName: builderContext.target.project,
@@ -10,7 +10,10 @@ import {
ProjectConfiguration,
ProjectsConfigurations,
} from '../../config/workspace-json-project-json';
import { mergeProjectConfigurationIntoProjectsConfigurations } from '../../project-graph/utils/project-configuration-utils';
import {
mergeProjectConfigurationIntoRootMap,
readProjectConfigurationsFromRootMap,
} from '../../project-graph/utils/project-configuration-utils';
import { retrieveProjectConfigurationPathsWithoutPluginInference } from '../../project-graph/utils/retrieve-workspace-files';
import { output } from '../../utils/output';
import { PackageJson } from '../../utils/package-json';
@@ -183,8 +186,6 @@ export function getRelativeProjectJsonSchemaPath(
function readAndCombineAllProjectConfigurations(tree: Tree): {
[name: string]: ProjectConfiguration;
} {
const nxJson = readNxJson(tree);
/**
* We can't update projects that come from plugins anyways, so we are going
* to ignore them for now. Plugins should add their own add/create/update methods
@@ -207,17 +208,12 @@ function readAndCombineAllProjectConfigurations(tree: Tree): {
(r) => deletedFiles.indexOf(r) === -1
);
const rootMap: Map<string, string> = new Map();
return projectFiles.reduce((projects, projectFile) => {
const rootMap: Map<string, ProjectConfiguration> = new Map();
for (const projectFile of projectFiles) {
if (basename(projectFile) === 'project.json') {
const json = readJson(tree, projectFile);
const config = buildProjectFromProjectJson(json, projectFile);
mergeProjectConfigurationIntoProjectsConfigurations(
projects,
rootMap,
config,
projectFile
);
mergeProjectConfigurationIntoRootMap(rootMap, config, projectFile);
} else {
const packageJson = readJson<PackageJson>(tree, projectFile);
const config = buildProjectConfigurationFromPackageJson(
@@ -225,8 +221,7 @@ function readAndCombineAllProjectConfigurations(tree: Tree): {
projectFile,
readNxJson(tree)
);
mergeProjectConfigurationIntoProjectsConfigurations(
projects,
mergeProjectConfigurationIntoRootMap(
rootMap,
// Inferred targets, tags, etc don't show up when running generators
// This is to help avoid running into issues when trying to update the workspace
@@ -237,8 +232,9 @@ function readAndCombineAllProjectConfigurations(tree: Tree): {
projectFile
);
}
return projects;
}, {});
}
return readProjectConfigurationsFromRootMap(rootMap);
}
/**
+1
View File
@@ -743,6 +743,7 @@ class TaskHasherImpl {
runtime,
{
windowsHide: true,
cwd: workspaceRoot,
},
(err, stdout, stderr) => {
if (err) {
+11 -2
View File
@@ -1,9 +1,14 @@
use std::fs;
use std::path::PathBuf;
use fs_extra::error::ErrorKind;
#[napi]
pub fn remove(src: String) -> anyhow::Result<()> {
fs_extra::remove_items(&[src]).map_err(anyhow::Error::from)
fs_extra::remove_items(&[src]).map_err(|err| match err.kind {
ErrorKind::Io(err_kind) => anyhow::Error::new(err_kind),
_ => anyhow::Error::new(err),
})
}
#[napi]
@@ -19,7 +24,11 @@ pub fn copy(src: String, dest: String) -> anyhow::Result<()> {
fs::create_dir_all(dest_parent)?;
}
fs_extra::copy_items(&[src], dest_parent, &copy_options)?;
fs_extra::copy_items(&[src], dest_parent, &copy_options).map_err(|err| match err.kind {
ErrorKind::Io(err_kind) => anyhow::Error::new(err_kind),
_ => anyhow::Error::new(err),
})?;
Ok(())
}
+5
View File
@@ -4,6 +4,8 @@ use std::path::PathBuf;
use tracing::trace;
use watchexec_events::{Event, Tag};
use crate::native::watch::utils::transform_event;
#[napi(string_enum)]
#[derive(Debug)]
/// Newly created files will have the `update` EventType as well.
@@ -52,6 +54,9 @@ pub(super) struct WatchEventInternal {
impl From<&Event> for WatchEventInternal {
fn from(value: &Event) -> Self {
let transformed = transform_event(value);
let value = transformed.as_ref().unwrap_or(value);
let path = value.paths().next().expect("there should always be a path");
let event_kind = value
+36 -1
View File
@@ -1,6 +1,10 @@
use ignore::WalkBuilder;
use ignore_files::IgnoreFile;
use std::path::PathBuf;
use once_cell::sync::Lazy;
use os_type::{OSInformation, OSType};
use std::{fs, path::PathBuf};
use tracing::trace;
use watchexec_events::{Event, Tag};
pub(super) fn get_ignore_files<T: AsRef<str>>(root: T) -> Vec<IgnoreFile> {
let root = root.as_ref();
@@ -45,3 +49,34 @@ pub(super) fn get_ignore_files<T: AsRef<str>>(root: T) -> Vec<IgnoreFile> {
// .map(|result| result.path().into())
// .collect()
// }
static OS_PLATFORM: Lazy<OSInformation> = Lazy::new(os_type::current_platform);
pub(super) fn transform_event(watch_event: &Event) -> Option<Event> {
if OS_PLATFORM.os_type == OSType::Debian {
let tags = watch_event
.tags
.clone()
.into_iter()
.map(|tag| match tag {
Tag::Path { path, file_type } => {
trace!("canonicalizing {:?}", path);
let real_path = fs::canonicalize(&path).unwrap_or(path);
trace!("real path {:?}", real_path);
Tag::Path {
path: real_path,
file_type,
}
}
_ => tag,
})
.collect();
Some(Event {
tags,
metadata: watch_event.metadata.clone(),
})
} else {
None
}
}
@@ -6,6 +6,8 @@ use watchexec_events::filekind::{CreateKind, FileEventKind, ModifyKind, RemoveKi
use watchexec_events::{Event, FileType, Priority, Source, Tag};
use watchexec_filterer_ignore::IgnoreFilterer;
use crate::native::watch::utils::transform_event;
#[derive(Debug)]
pub struct WatchFilterer {
pub inner: IgnoreFilterer,
@@ -13,7 +15,10 @@ pub struct WatchFilterer {
/// Used to filter out events that that come from watchexec
impl Filterer for WatchFilterer {
fn check_event(&self, event: &Event, priority: Priority) -> Result<bool, RuntimeError> {
fn check_event(&self, watch_event: &Event, priority: Priority) -> Result<bool, RuntimeError> {
let transformed = transform_event(watch_event);
let event = transformed.as_ref().unwrap_or(watch_event);
if !self.inner.check_event(event, priority)? {
return Ok(false);
}
+6 -2
View File
@@ -34,11 +34,15 @@ export const processProjectGraph: ProjectGraphProcessor = async (
let parsedLockFile: ProjectGraph;
if (lockFileNeedsReprocessing(lockHash)) {
parsedLockFile = parseLockFile();
writeLastProcessedLockfileHash(lockHash, parsedLockFile);
if (parsedLockFile) {
writeLastProcessedLockfileHash(lockHash, parsedLockFile);
}
} else {
parsedLockFile = readParsedLockFile();
}
builder.mergeProjectGraph(parsedLockFile);
if (parsedLockFile) {
builder.mergeProjectGraph(parsedLockFile);
}
}
}
@@ -365,6 +365,11 @@ describe('yarn LockFile utility', () => {
'node_modules/yargs/package.json': '{"version": "17.6.2"}',
'node_modules/yargs-parser/package.json': '{"version": "21.1.1"}',
'node_modules/yocto-queue/package.json': '{"version": "0.1.0"}',
'node_modules/@types/prop-types/package.json': '{"version": "15.7.5"}',
'node_modules/@docusaurus/core/package.json': '{"version": "2.4.1"}',
'node_modules/@docusaurus/react-loadable/package.json':
'{"version": "5.5.2"}',
'node_modules/react-loadable/package.json': '{"version": "5.5.2"}',
};
vol.fromJSON(fileSys, '/root');
});
@@ -620,6 +625,145 @@ describe('yarn LockFile utility', () => {
prunedLockFile.split('\n').slice(2)
);
});
describe('alias duplicate', () => {
it('should parse yarn berry', async () => {
const lockFile = `# This file is generated by running "yarn install" inside your project.
# Manual changes might be lost - proceed with caution!
__metadata:
version: 6
cacheKey: 8
"@docusaurus/core@npm:2.4.1":
version: 2.4.1
resolution: "@docusaurus/core@npm:2.4.1"
dependencies:
"@docusaurus/react-loadable": 5.5.2
react-loadable: "npm:@docusaurus/react-loadable@5.5.2"
bin:
docusaurus: bin/docusaurus.mjs
checksum: 40c887ef662f7679d803695d4193268c2c177c6d4e13b43b56cc519322522a1608b4bfc4999f6355be778ca7a0256f0d27ab18a19b352a9da1aed66e2644dc82
languageName: node
linkType: hard
"@docusaurus/react-loadable@npm:5.5.2, react-loadable@npm:@docusaurus/react-loadable@5.5.2":
version: 5.5.2
resolution: "@docusaurus/react-loadable@npm:5.5.2"
checksum: 930fb9e2936412a12461f210acdc154a433283921ca43ac3fc3b84cb6c12eb738b3a3719373022bf68004efeb1a928dbe36c467d7a1f86454ed6241576d936e7
languageName: node
linkType: hard
`;
const packageJson: PackageJson = {
name: '@my-ns/example',
version: '0.0.1',
type: 'commonjs',
dependencies: {
'@docusaurus/core': '2.4.1',
},
};
const builder = new ProjectGraphBuilder();
parseYarnLockfile(lockFile, packageJson, builder);
const graph = builder.getUpdatedProjectGraph();
expect(graph.externalNodes).toMatchInlineSnapshot(`
{
"npm:@docusaurus/core": {
"data": {
"hash": "40c887ef662f7679d803695d4193268c2c177c6d4e13b43b56cc519322522a1608b4bfc4999f6355be778ca7a0256f0d27ab18a19b352a9da1aed66e2644dc82",
"packageName": "@docusaurus/core",
"version": "2.4.1",
},
"name": "npm:@docusaurus/core",
"type": "npm",
},
"npm:@docusaurus/react-loadable": {
"data": {
"hash": "930fb9e2936412a12461f210acdc154a433283921ca43ac3fc3b84cb6c12eb738b3a3719373022bf68004efeb1a928dbe36c467d7a1f86454ed6241576d936e7",
"packageName": "@docusaurus/react-loadable",
"version": "5.5.2",
},
"name": "npm:@docusaurus/react-loadable",
"type": "npm",
},
"npm:react-loadable": {
"data": {
"hash": "930fb9e2936412a12461f210acdc154a433283921ca43ac3fc3b84cb6c12eb738b3a3719373022bf68004efeb1a928dbe36c467d7a1f86454ed6241576d936e7",
"packageName": "react-loadable",
"version": "npm:@docusaurus/react-loadable@5.5.2",
},
"name": "npm:react-loadable",
"type": "npm",
},
}
`);
});
it('should parse yarn classic', async () => {
const lockFile = `# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@docusaurus/core@2.4.1":
version "2.4.1"
resolved "https://registry.yarnpkg.com/@docusaurus/core/-/core-2.4.1.tgz#4b8ff5766131ce3fbccaad0b1daf2ad4dc76f62d"
integrity sha512-SNsY7PshK3Ri7vtsLXVeAJGS50nJN3RgF836zkyUfAD01Fq+sAk5EwWgLw+nnm5KVNGDu7PRR2kRGDsWvqpo0g==
dependencies:
"@docusaurus/react-loadable" "5.5.2"
react-loadable "npm:@docusaurus/react-loadable@5.5.2"
"@docusaurus/react-loadable@5.5.2", "react-loadable@npm:@docusaurus/react-loadable@5.5.2":
version "5.5.2"
resolved "https://registry.yarnpkg.com/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz#81aae0db81ecafbdaee3651f12804580868fa6ce"
integrity sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ==
`;
const packageJson: PackageJson = {
name: '@my-ns/example',
version: '0.0.1',
type: 'commonjs',
dependencies: {
'@docusaurus/core': '2.4.1',
},
};
const builder = new ProjectGraphBuilder();
parseYarnLockfile(lockFile, packageJson, builder);
const graph = builder.getUpdatedProjectGraph();
expect(graph.externalNodes).toMatchInlineSnapshot(`
{
"npm:@docusaurus/core": {
"data": {
"hash": "sha512-SNsY7PshK3Ri7vtsLXVeAJGS50nJN3RgF836zkyUfAD01Fq+sAk5EwWgLw+nnm5KVNGDu7PRR2kRGDsWvqpo0g==",
"packageName": "@docusaurus/core",
"version": "2.4.1",
},
"name": "npm:@docusaurus/core",
"type": "npm",
},
"npm:@docusaurus/react-loadable": {
"data": {
"hash": "sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ==",
"packageName": "@docusaurus/react-loadable",
"version": "5.5.2",
},
"name": "npm:@docusaurus/react-loadable",
"type": "npm",
},
"npm:react-loadable": {
"data": {
"hash": "sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ==",
"packageName": "react-loadable",
"version": "npm:@docusaurus/react-loadable@5.5.2",
},
"name": "npm:react-loadable",
"type": "npm",
},
}
`);
});
});
});
describe('auxiliary tagged version ranges', () => {
@@ -50,13 +50,17 @@ export function parseYarnLockfile(
addDependencies(groupedDependencies, builder, keyMap);
}
function getPackageNames(keys: string): string[] {
const packageNames = new Set<string>();
function getPackageNameKeyPairs(keys: string): Map<string, Set<string>> {
const result = new Map<string, Set<string>>();
keys.split(', ').forEach((key) => {
const packageName = key.slice(0, key.indexOf('@', 1));
packageNames.add(packageName);
if (result.has(packageName)) {
result.get(packageName).add(key);
} else {
result.set(packageName, new Set([key]));
}
});
return Array.from(packageNames);
return result;
}
function addNodes(
@@ -79,16 +83,14 @@ function addNodes(
if (snapshot.linkType === 'soft' || keys.includes('@patch:')) {
return;
}
const packageNames = getPackageNames(keys);
packageNames.forEach((packageName) => {
const version = findVersion(
packageName,
keys.split(', ')[0],
snapshot,
isBerry
);
const nameKeyPairs = getPackageNameKeyPairs(keys);
nameKeyPairs.forEach((keySet, packageName) => {
const keysArray = Array.from(keySet);
// use key relevant to the package name
const version = findVersion(packageName, keysArray[0], snapshot, isBerry);
keys.split(', ').forEach((key) => {
// use keys linked to the extracted package name
keysArray.forEach((key) => {
// we don't need to keep duplicates, we can just track the keys
const existingNode = nodes.get(packageName)?.get(version);
if (existingNode) {
@@ -182,11 +184,12 @@ function findVersion(
snapshot: YarnDependency,
isBerry: boolean
): string {
const versionRange = key.slice(packageName.length + 1);
const versionRange = key.slice(key.indexOf('@', 1) + 1);
// check for alias packages
const isAlias = isBerry
? snapshot.resolution && !snapshot.resolution.startsWith(`${packageName}@`)
: versionRange.startsWith('npm:');
if (isAlias) {
return versionRange;
}
@@ -587,7 +587,10 @@ async function createVirtualWorkspace(config: VirtualWorkspaceConfig) {
return {
ctx: {
projectsConfigurations: projectConfigurations,
projectsConfigurations: {
version: 2,
projectConfigurations: projectConfigurations,
},
nxJsonConfiguration: nxJson,
filesToProcess: projectFileMap,
},
@@ -312,6 +312,32 @@ describe('workspace-projects', () => {
).toEqual({ a: 'a', b: 'my/project' });
});
it('should merge options when targets use executors with defaults', () => {
expect(
normalizeProjectTargets(
{
root: 'my/project',
targets: {
build: {
executor: '@nx/jest:jest',
options: {
a: 'a',
},
},
},
},
{
'@nx/jest:jest': {
options: {
b: 'b',
},
},
},
'build'
).build.options
).toEqual({ a: 'a', b: 'b' });
});
it('should not merge options when targets use different executors', () => {
expect(
normalizeProjectTargets(
@@ -1,14 +1,10 @@
import { join } from 'path';
import { existsSync } from 'fs';
import { workspaceRoot } from '../../utils/workspace-root';
import {
ProjectGraphProcessorContext,
ProjectGraphProjectNode,
} from '../../config/project-graph';
import { mergeNpmScriptsWithTargets } from '../../utils/project-graph-utils';
import { ProjectGraphBuilder } from '../project-graph-builder';
import { PackageJson } from '../../utils/package-json';
import { readJsonFile } from '../../utils/fileutils';
import { NxJsonConfiguration } from '../../config/nx-json';
import {
ProjectConfiguration,
@@ -45,36 +41,6 @@ export async function normalizeProjectNodes(
for (const key of projects) {
const p = ctx.projectsConfigurations.projects[key];
const projectRoot = join(workspaceRoot, p.root);
// Todo(@AgentEnder) we can move a lot of this to
// builtin plugin inside workspaces.ts, but there would be some functional differences
// - The plugin would only apply to package.json files found via the workspaces globs
// - This means that scripts / tags / etc from the `nx` property wouldn't be read if a project
// is being found by project.json and not included in the workspaces configuration. Maybe this is fine?
if (existsSync(join(projectRoot, 'package.json'))) {
p.targets = mergeNpmScriptsWithTargets(projectRoot, p.targets);
try {
const { nx }: PackageJson = readJsonFile(
join(projectRoot, 'package.json')
);
if (nx?.tags) {
p.tags = [...(p.tags || []), ...nx.tags];
}
if (nx?.implicitDependencies) {
p.implicitDependencies = [
...(p.implicitDependencies || []),
...nx.implicitDependencies,
];
}
if (nx?.namedInputs) {
p.namedInputs = { ...(p.namedInputs || {}), ...nx.namedInputs };
}
} catch {
// ignore json parser errors
}
}
p.implicitDependencies = normalizeImplicitDependencies(
key,
@@ -134,9 +100,8 @@ export function normalizeProjectTargets(
// We need to know the executor for use in readTargetDefaultsForTarget,
// but we haven't resolved the `command` syntactic sugar yet.
const executor =
targets[target].executor ?? targets[target].command
? 'nx:run-commands'
: null;
targets[target].executor ??
(targets[target].command ? 'nx:run-commands' : null);
// Allows things like { targetDefaults: { build: { command: tsc } } }
const defaults = resolveCommandSyntacticSugar(
@@ -23,10 +23,7 @@ import {
import { readJsonFile } from '../utils/fileutils';
import { NxJsonConfiguration } from '../config/nx-json';
import { ProjectGraphBuilder } from './project-graph-builder';
import {
ProjectConfiguration,
ProjectsConfigurations,
} from '../config/workspace-json-project-json';
import { ProjectConfiguration } from '../config/workspace-json-project-json';
import { readNxJson } from '../config/configuration';
import { existsSync } from 'fs';
import { PackageJson } from '../utils/package-json';
@@ -49,7 +46,7 @@ export function getProjectFileMap(): {
}
export async function buildProjectGraphUsingProjectFileMap(
projectsConfigurations: ProjectsConfigurations,
projects: Record<string, ProjectConfiguration>,
externalNodes: Record<string, ProjectGraphExternalNode>,
projectFileMap: ProjectFileMap,
allWorkspaceFiles: FileData[],
@@ -64,7 +61,7 @@ export async function buildProjectGraphUsingProjectFileMap(
const nxJson = readNxJson();
const projectGraphVersion = '6.0';
assertWorkspaceValidity(projectsConfigurations, nxJson);
assertWorkspaceValidity(projects, nxJson);
const packageJsonDeps = readCombinedDeps();
const rootTsConfig = readRootTsConfig();
@@ -75,7 +72,7 @@ export async function buildProjectGraphUsingProjectFileMap(
!shouldRecomputeWholeGraph(
fileMap,
packageJsonDeps,
projectsConfigurations,
projects,
nxJson,
rootTsConfig
);
@@ -89,7 +86,7 @@ export async function buildProjectGraphUsingProjectFileMap(
}
const context = createContext(
projectsConfigurations,
projects,
nxJson,
projectFileMap,
filesToProcess
@@ -185,27 +182,27 @@ async function buildProjectGraphUsingContext(
}
function createContext(
projectsConfigurations: ProjectsConfigurations,
projects: Record<string, ProjectConfiguration>,
nxJson: NxJsonConfiguration,
fileMap: ProjectFileMap,
filesToProcess: ProjectFileMap
): ProjectGraphProcessorContext {
const projects = Object.keys(projectsConfigurations.projects).reduce(
(map, projectName) => {
map[projectName] = {
...projectsConfigurations.projects[projectName],
};
return map;
},
{} as Record<string, ProjectConfiguration>
);
const clonedProjects = Object.keys(projects).reduce((map, projectName) => {
map[projectName] = {
...projects[projectName],
};
return map;
}, {} as Record<string, ProjectConfiguration>);
return {
nxJsonConfiguration: nxJson,
projectsConfigurations,
workspace: {
...projectsConfigurations,
version: 2,
projects: clonedProjects,
...nxJson,
projects,
},
projectsConfigurations: {
version: 2,
projects: clonedProjects,
},
fileMap,
filesToProcess,
+13 -9
View File
@@ -1,4 +1,3 @@
import { Workspaces } from '../config/workspaces';
import { execSync } from 'child_process';
import { existsSync, readFileSync } from 'fs';
import { extname, join, relative, sep } from 'path';
@@ -15,6 +14,7 @@ import {
} from './project-graph';
import { toOldFormat } from '../adapter/angular-json';
import { getIgnoreObject } from '../utils/ignore';
import { retrieveProjectConfigurationsSync } from './utils/retrieve-workspace-files';
export interface Change {
type: string;
@@ -119,20 +119,28 @@ function defaultReadFileAtRevision(
}
}
/**
* TODO(v18): Remove this function
* @deprecated To get projects use {@link retrieveProjectConfigurations} instead
*/
export function readWorkspaceConfig(opts: {
format: 'angularCli' | 'nx';
path?: string;
}): ProjectsConfigurations {
let configuration: ProjectsConfigurations | null = null;
const root = opts.path || process.cwd();
const nxJson = readNxJson(root);
try {
const projectGraph = readCachedProjectGraph();
configuration = {
...readNxJson(),
...nxJson,
...readProjectsConfigurationFromProjectGraph(projectGraph),
};
} catch {
const ws = new Workspaces(opts.path || process.cwd());
configuration = ws.readProjectsConfigurations();
configuration = {
version: 2,
projects: retrieveProjectConfigurationsSync(root, nxJson).projectNodes,
};
}
if (opts.format === 'angularCli') {
return toOldFormat(configuration);
@@ -155,8 +163,4 @@ export function readPackageJson(): any {
// Original Exports
export { FileData };
// TODO(17): Remove these exports
export {
readNxJson,
readAllWorkspaceConfiguration,
workspaceLayout,
} from '../config/configuration';
export { readNxJson, workspaceLayout } from '../config/configuration';
@@ -4,7 +4,7 @@ import {
ProjectFileMapCache,
shouldRecomputeWholeGraph,
} from './nx-deps-cache';
import { ProjectsConfigurations } from '../config/workspace-json-project-json';
import { ProjectConfiguration } from '../config/workspace-json-project-json';
import { NxJsonConfiguration } from '../config/nx-json';
import { nxVersion } from '../utils/versions';
@@ -299,11 +299,10 @@ describe('nx deps utils', () => {
return { ...defaults, ...p };
}
function createProjectsConfiguration(p: any): ProjectsConfigurations {
const defaults = {
projects: { mylib: {} },
} as any;
return { ...defaults, ...p };
function createProjectsConfiguration(
p: any
): Record<string, ProjectConfiguration> {
return { mylib: {}, ...p };
}
function createNxJson(p: Partial<NxJsonConfiguration>): NxJsonConfiguration {
@@ -8,7 +8,7 @@ import {
ProjectFileMap,
ProjectGraph,
} from '../config/project-graph';
import { ProjectsConfigurations } from '../config/workspace-json-project-json';
import { ProjectConfiguration } from '../config/workspace-json-project-json';
import { projectGraphCacheDirectory } from '../utils/cache-directory';
import {
directoryExists,
@@ -171,7 +171,7 @@ export function writeCache(
export function shouldRecomputeWholeGraph(
cache: ProjectFileMapCache,
packageJsonDeps: Record<string, string>,
projects: ProjectsConfigurations,
projects: Record<string, ProjectConfiguration>,
nxJson: NxJsonConfiguration,
tsConfig: { compilerOptions: { paths: { [k: string]: any } } }
): boolean {
@@ -184,7 +184,7 @@ export function shouldRecomputeWholeGraph(
// we have a cached project that is no longer present
const cachedNodes = Object.keys(cache.projectFileMap);
if (cachedNodes.some((p) => projects.projects[p] === undefined)) {
if (cachedNodes.some((p) => projects[p] === undefined)) {
return true;
}
@@ -54,6 +54,9 @@ export function readCachedProjectConfiguration(
return node.data;
}
/**
* Get the {@link ProjectsConfigurations} from the {@link ProjectGraph}
*/
export function readProjectsConfigurationFromProjectGraph(
projectGraph: ProjectGraph
): ProjectsConfigurations {
@@ -81,7 +84,7 @@ export async function buildProjectGraphWithoutDaemon() {
const cacheEnabled = process.env.NX_CACHE_PROJECT_GRAPH !== 'false';
return (
await buildProjectGraphUsingProjectFileMap(
projectConfigurations,
projectConfigurations.projects,
externalNodes,
projectFileMap,
allWorkspaceFiles,
@@ -117,7 +120,7 @@ function handleProjectGraphError(opts: { exitOnError: boolean }, e) {
* * It is running in the docker container.
* * The daemon process is disabled because of the previous error when starting the daemon.
* * `NX_DAEMON` is set to `false`.
* * `useDaemon` is set to false in `nx.json`
* * `useDaemonProcess` is set to false in the options of the tasks runner inside `nx.json`
*
* `NX_DAEMON` env variable takes precedence:
* * If it is set to true, the daemon will always be used.
@@ -1,6 +1,11 @@
import { TargetConfiguration } from '../../config/workspace-json-project-json';
import {
ProjectConfiguration,
TargetConfiguration,
} from '../../config/workspace-json-project-json';
import {
mergeProjectConfigurationIntoRootMap,
mergeTargetConfigurations,
readProjectConfigurationsFromRootMap,
readTargetDefaultsForTarget,
} from './project-configuration-utils';
@@ -352,3 +357,146 @@ describe('target defaults', () => {
});
});
});
describe('mergeProjectConfigurationIntoRootMap', () => {
it('should merge targets from different configurations', () => {
const rootMap = new RootMapBuilder()
.addProject({
root: 'libs/lib-a',
name: 'lib-a',
targets: {
echo: {
command: 'echo lib-a',
},
},
})
.getRootMap();
mergeProjectConfigurationIntoRootMap(
rootMap,
{
root: 'libs/lib-a',
name: 'lib-a',
targets: {
build: {
command: 'tsc',
},
},
},
'inferred-project-config-file.ts'
);
expect(rootMap.get('libs/lib-a')).toMatchInlineSnapshot(`
{
"name": "lib-a",
"root": "libs/lib-a",
"targets": {
"build": {
"command": "tsc",
},
"echo": {
"command": "echo lib-a",
},
},
}
`);
});
it("shouldn't overwrite project name, unless merging project from project.json", () => {
const rootMap = new RootMapBuilder()
.addProject({
name: 'bad-name',
root: 'libs/lib-a',
})
.getRootMap();
mergeProjectConfigurationIntoRootMap(
rootMap,
{
name: 'other-bad-name',
root: 'libs/lib-a',
},
'libs/lib-a/package.json'
);
expect(rootMap.get('libs/lib-a').name).toEqual('bad-name');
mergeProjectConfigurationIntoRootMap(
rootMap,
{
name: 'lib-a',
root: 'libs/lib-a',
},
'libs/lib-a/project.json'
);
expect(rootMap.get('libs/lib-a').name).toEqual('lib-a');
});
});
describe('readProjectsConfigurationsFromRootMap', () => {
it('should error if multiple roots point to the same project', () => {
const rootMap = new RootMapBuilder()
.addProject({
name: 'lib',
root: 'apps/lib-a',
})
.addProject({
name: 'lib',
root: 'apps/lib-b',
})
.getRootMap();
expect(() => {
readProjectConfigurationsFromRootMap(rootMap);
}).toThrowErrorMatchingInlineSnapshot(`
"The following projects are defined in multiple locations:
- lib:
- apps/lib-a
- apps/lib-b
To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name."
`);
});
it('should read root map into standard projects configurations form', () => {
const rootMap = new RootMapBuilder()
.addProject({
name: 'lib-a',
root: 'libs/a',
})
.addProject({
name: 'lib-b',
root: 'libs/b',
})
.addProject({
name: 'lib-shared-b',
root: 'libs/shared/b',
})
.getRootMap();
expect(readProjectConfigurationsFromRootMap(rootMap))
.toMatchInlineSnapshot(`
{
"lib-a": {
"name": "lib-a",
"root": "libs/a",
},
"lib-b": {
"name": "lib-b",
"root": "libs/b",
},
"lib-shared-b": {
"name": "lib-shared-b",
"root": "libs/shared/b",
},
}
`);
});
});
class RootMapBuilder {
private rootMap: Map<string, ProjectConfiguration> = new Map();
addProject(p: ProjectConfiguration) {
this.rootMap.set(p.root, p);
return this;
}
getRootMap() {
return this.rootMap;
}
}
@@ -8,53 +8,42 @@ import {
ProjectConfiguration,
TargetConfiguration,
} from '../../config/workspace-json-project-json';
import { readJsonFile } from '../../utils/fileutils';
import { NX_PREFIX } from '../../utils/logger';
import { NxPluginV2 } from '../../utils/nx-plugin';
import { workspaceRoot } from '../../utils/workspace-root';
import minimatch = require('minimatch');
export function mergeProjectConfigurationIntoProjectsConfigurations(
// projectName -> ProjectConfiguration
existingProjects: Record<string, ProjectConfiguration>,
// projectRoot -> projectName
existingProjectRootMap: Map<string, string>,
export function mergeProjectConfigurationIntoRootMap(
projectRootMap: Map<string, ProjectConfiguration>,
project: ProjectConfiguration,
// project.json is a special case, so we need to detect it.
file: string
): void {
let matchingProjectName = existingProjectRootMap.get(project.root);
const matchingProject = projectRootMap.get(project.root);
if (!matchingProjectName) {
existingProjects[project.name] = project;
existingProjectRootMap.set(project.root, project.name);
if (!matchingProject) {
projectRootMap.set(project.root, project);
return;
// There are some special cases for handling project.json - mainly
// that it should override any name the project already has.
} else if (
project.name &&
project.name !== matchingProjectName &&
project.name !== matchingProject.name &&
basename(file) === 'project.json'
) {
// Copy config to new name
existingProjects[project.name] = existingProjects[matchingProjectName];
// Update name in project config
existingProjects[project.name].name = project.name;
// Update root map to point to new name
existingProjectRootMap[project.root] = project.name;
// Remove entry for old name
delete existingProjects[matchingProjectName];
// Update name that config should be merged to
matchingProjectName = project.name;
// `name` inside project.json overrides any names from
// inference plugins
matchingProject.name = project.name;
}
const matchingProject = existingProjects[matchingProjectName];
// This handles top level properties that are overwritten. `srcRoot`, `projectType`, or fields that Nx doesn't know about.
// This handles top level properties that are overwritten.
// e.g. `srcRoot`, `projectType`, or other fields that shouldn't be extended
// Note: `name` is set specifically here to keep it from changing. The name is
// always determined by the first inference plugin to ID a project, unless it has
// a project.json in which case it was already updated above.
const updatedProjectConfiguration = {
...matchingProject,
...project,
name: matchingProjectName,
name: matchingProject.name,
};
// The next blocks handle properties that should be themselves merged (e.g. targets, tags, and implicit dependencies)
@@ -83,11 +72,10 @@ export function mergeProjectConfigurationIntoProjectsConfigurations(
};
}
if (updatedProjectConfiguration.name !== matchingProject.name) {
delete existingProjects[matchingProject.name];
}
existingProjects[updatedProjectConfiguration.name] =
updatedProjectConfiguration;
projectRootMap.set(
updatedProjectConfiguration.root,
updatedProjectConfiguration
);
}
export function buildProjectsConfigurationsFromProjectPathsAndPlugins(
@@ -99,8 +87,7 @@ export function buildProjectsConfigurationsFromProjectPathsAndPlugins(
projects: Record<string, ProjectConfiguration>;
externalNodes: Record<string, ProjectGraphExternalNode>;
} {
const projectRootMap: Map<string, string> = new Map();
const projects: Record<string, ProjectConfiguration> = {};
const projectRootMap: Map<string, ProjectConfiguration> = new Map();
const externalNodes: Record<string, ProjectGraphExternalNode> = {};
// We push the nx core node builder onto the end, s.t. it overwrites any user specified behavior
@@ -119,13 +106,12 @@ export function buildProjectsConfigurationsFromProjectPathsAndPlugins(
if (minimatch(file, pattern)) {
const { projects: projectNodes, externalNodes: pluginExternalNodes } =
configurationConstructor(file, {
projectsConfigurations: projects,
nxJsonConfiguration: nxJson,
workspaceRoot: root,
});
for (const node in projectNodes) {
mergeProjectConfigurationIntoProjectsConfigurations(
projects,
projectNodes[node].name ??= node;
mergeProjectConfigurationIntoRootMap(
projectRootMap,
projectNodes[node],
file
@@ -136,7 +122,48 @@ export function buildProjectsConfigurationsFromProjectPathsAndPlugins(
}
}
return { projects, externalNodes };
return {
projects: readProjectConfigurationsFromRootMap(projectRootMap),
externalNodes,
};
}
export function readProjectConfigurationsFromRootMap(
projectRootMap: Map<string, ProjectConfiguration>
) {
const projects: Record<string, ProjectConfiguration> = {};
// If there are projects that have the same name, that is an error.
// This object tracks name -> (all roots of projects with that name)
// to provide better error messaging.
const errors: Map<string, string[]> = new Map();
for (const [root, configuration] of projectRootMap.entries()) {
if (!configuration.name) {
throw new Error(`Project at ${root} has no name provided.`);
} else if (configuration.name in projects) {
let rootErrors = errors.get(configuration.name) ?? [
projects[configuration.name].root,
];
rootErrors.push(root);
errors.set(configuration.name, rootErrors);
} else {
projects[configuration.name] = configuration;
}
}
if (errors.size > 0) {
throw new Error(
[
`The following projects are defined in multiple locations:`,
...Array.from(errors.entries()).map(([project, roots]) =>
[`- ${project}: `, ...roots.map((r) => ` - ${r}`)].join('\n')
),
'',
"To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.",
].join('\n')
);
}
return projects;
}
export function mergeTargetConfigurations(
@@ -1,13 +1,12 @@
import { performance } from 'perf_hooks';
import { getNxRequirePaths } from '../../utils/installation-directory';
import { readJsonFile } from '../../utils/fileutils';
import { join } from 'path';
import {
ProjectConfiguration,
ProjectsConfigurations,
} from '../../config/workspace-json-project-json';
import {
mergeAngularJsonAndProjects,
NxAngularJsonPlugin,
NX_ANGULAR_JSON_PLUGIN_NAME,
shouldMergeAngularProjects,
} from '../../adapter/angular-json';
import { NxJsonConfiguration, readNxJson } from '../../config/nx-json';
@@ -22,6 +21,7 @@ import { buildProjectsConfigurationsFromProjectPathsAndPlugins } from './project
import {
loadNxPlugins,
loadNxPluginsSync,
NxPlugin,
NxPluginV2,
} from '../../utils/nx-plugin';
@@ -99,14 +99,71 @@ export async function retrieveProjectConfigurations(
externalNodes: Record<string, ProjectGraphExternalNode>;
projectNodes: Record<string, ProjectConfiguration>;
}> {
const { getProjectConfigurations } =
require('../../native') as typeof import('../../native');
const plugins = await loadNxPlugins(
nxJson?.plugins ?? [],
getNxRequirePaths(workspaceRoot),
workspaceRoot
);
const globs = configurationGlobs(workspaceRoot, plugins);
return _retrieveProjectConfigurations(workspaceRoot, nxJson, plugins, globs);
}
export async function retrieveProjectConfigurationsWithAngularProjects(
workspaceRoot: string,
nxJson: NxJsonConfiguration
): Promise<{
externalNodes: Record<string, ProjectGraphExternalNode>;
projectNodes: Record<string, ProjectConfiguration>;
}> {
const plugins = await loadNxPlugins(
nxJson?.plugins ?? [],
getNxRequirePaths(workspaceRoot),
workspaceRoot
);
if (
shouldMergeAngularProjects(workspaceRoot, true) &&
!plugins.some((p) => p.name === NX_ANGULAR_JSON_PLUGIN_NAME)
) {
plugins.push(NxAngularJsonPlugin);
}
const globs = configurationGlobs(workspaceRoot, plugins);
return _retrieveProjectConfigurations(workspaceRoot, nxJson, plugins, globs);
}
/**
* @deprecated Use {@link retrieveProjectConfigurations} instead.
*/
export function retrieveProjectConfigurationsSync(
workspaceRoot: string,
nxJson: NxJsonConfiguration
): {
externalNodes: Record<string, ProjectGraphExternalNode>;
projectNodes: Record<string, ProjectConfiguration>;
} {
const plugins = loadNxPluginsSync(
nxJson?.plugins ?? [],
getNxRequirePaths(workspaceRoot),
workspaceRoot
);
const globs = configurationGlobs(workspaceRoot, plugins);
return _retrieveProjectConfigurations(workspaceRoot, nxJson, plugins, globs);
}
function _retrieveProjectConfigurations(
workspaceRoot: string,
nxJson: NxJsonConfiguration,
plugins: NxPluginV2[],
globs: string[]
): {
externalNodes: Record<string, ProjectGraphExternalNode>;
projectNodes: Record<string, ProjectConfiguration>;
} {
const { getProjectConfigurations } =
require('../../native') as typeof import('../../native');
return getProjectConfigurations(workspaceRoot, globs, (configs: string[]) => {
const projectConfigurations = createProjectConfigurations(
workspaceRoot,
@@ -227,12 +284,6 @@ function createProjectConfigurations(
let projectConfigurations = projects;
if (shouldMergeAngularProjects(workspaceRoot, false)) {
projectConfigurations = mergeAngularJsonAndProjects(
projectConfigurations,
workspaceRoot
);
}
performance.mark('build-project-configs:end');
performance.measure(
'build-project-configs',
@@ -27,7 +27,7 @@ export class TaskProfilingLifeCycle implements LifeCycle {
}
for (let t of tasks) {
this.timings[t.id] = {
perfStart: performance.now(),
perfStart: Date.now(),
};
}
}
@@ -43,7 +43,7 @@ export class TaskProfilingLifeCycle implements LifeCycle {
if (tr.task.endTime) {
this.timings[tr.task.id].perfEnd = tr.task.endTime;
} else {
this.timings[tr.task.id].perfEnd = performance.now();
this.timings[tr.task.id].perfEnd = Date.now();
}
}
this.recordTaskCompletions(taskResults, metadata);
@@ -24,7 +24,6 @@ import { createTaskGraph } from './create-task-graph';
import { findCycle, makeAcyclic } from './task-graph-utils';
import { TargetDependencyConfig } from '../config/workspace-json-project-json';
import { handleErrors } from '../utils/params';
import { Workspaces } from '../config/workspaces';
import {
DaemonBasedTaskHasher,
InProcessTaskHasher,
@@ -1,5 +1,4 @@
import { TasksSchedule } from './tasks-schedule';
import { Workspaces } from '../config/workspaces';
import { removeTasksFromTaskGraph } from './utils';
import { Task, TaskGraph } from '../config/task-graph';
import { DependencyType, ProjectGraph } from '../config/project-graph';
@@ -1,5 +1,3 @@
import { Workspaces } from '../config/workspaces';
import {
calculateReverseDeps,
getExecutorForTask,
@@ -10,7 +8,6 @@ import { DefaultTasksRunnerOptions } from './default-tasks-runner';
import { TaskHasher } from '../hasher/task-hasher';
import { Task, TaskGraph } from '../config/task-graph';
import { ProjectGraph } from '../config/project-graph';
import { NxJsonConfiguration } from '../config/nx-json';
import { hashTask } from '../hasher/hash-task';
import { findAllProjectNodeDependencies } from '../utils/project-graph-utils';
import { reverse } from '../project-graph/operators';
@@ -1,38 +1,36 @@
import { assertWorkspaceValidity } from './assert-workspace-validity';
describe('assertWorkspaceValidity', () => {
let mockWorkspaceJson: any;
let mockProjects: any;
beforeEach(() => {
mockWorkspaceJson = {
projects: {
app1: {},
'app1-e2e': {},
app2: {},
'app2-e2e': {},
lib1: {},
lib2: {},
},
mockProjects = {
app1: {},
'app1-e2e': {},
app2: {},
'app2-e2e': {},
lib1: {},
lib2: {},
};
});
it('should not throw for a valid workspace', () => {
assertWorkspaceValidity(mockWorkspaceJson, {});
assertWorkspaceValidity(mockProjects, {});
});
it('should not throw for a project-level implicit dependency with a glob', () => {
mockWorkspaceJson.projects.app2.implicitDependencies = ['lib*'];
mockProjects.app2.implicitDependencies = ['lib*'];
expect(() => {
assertWorkspaceValidity(mockWorkspaceJson, {});
assertWorkspaceValidity(mockProjects, {});
}).not.toThrow();
});
it('should throw for an invalid project-level implicit dependency', () => {
mockWorkspaceJson.projects.app2.implicitDependencies = ['invalidproj'];
mockWorkspaceJson.projects.lib1.implicitDependencies = '*';
mockProjects.app2.implicitDependencies = ['invalidproj'];
mockProjects.lib1.implicitDependencies = '*';
expect(() => assertWorkspaceValidity(mockWorkspaceJson, {}))
expect(() => assertWorkspaceValidity(mockProjects, {}))
.toThrowErrorMatchingInlineSnapshot(`
"Configuration Error
The following implicitDependencies should be an array of strings:
@@ -45,10 +43,10 @@ describe('assertWorkspaceValidity', () => {
});
it('should throw for an invalid project-level implicit dependency with glob', () => {
mockWorkspaceJson.projects.app2.implicitDependencies = ['invalid*'];
mockProjects.app2.implicitDependencies = ['invalid*'];
try {
assertWorkspaceValidity(mockWorkspaceJson, {});
assertWorkspaceValidity(mockProjects, {});
fail('should not reach');
} catch (e) {
expect(e.message).toContain(
@@ -1,16 +1,16 @@
import { ProjectsConfigurations } from '../config/workspace-json-project-json';
import { ProjectConfiguration } from '../config/workspace-json-project-json';
import { NxJsonConfiguration } from '../config/nx-json';
import { findMatchingProjects } from './find-matching-projects';
import { output } from './output';
import { ProjectGraphProjectNode } from '../config/project-graph';
export function assertWorkspaceValidity(
projectsConfigurations: ProjectsConfigurations,
projects: Record<string, ProjectConfiguration>,
nxJson: NxJsonConfiguration
) {
const projectNames = Object.keys(projectsConfigurations.projects);
const projectNames = Object.keys(projects);
const projectGraphNodes = projectNames.reduce((graph, project) => {
const projectConfiguration = projectsConfigurations.projects[project];
const projectConfiguration = projects[project];
graph[project] = {
name: project,
type: projectConfiguration.projectType === 'library' ? 'lib' : 'app', // missing fallback to `e2e`
@@ -21,10 +21,6 @@ export function assertWorkspaceValidity(
return graph;
}, {} as Record<string, ProjectGraphProjectNode>);
const projects = {
...projectsConfigurations.projects,
};
const invalidImplicitDependencies = new Map<string, string[]>();
if (nxJson.implicitDependencies) {
@@ -110,7 +106,7 @@ function detectAndSetInvalidProjectGlobValues(
map: Map<string, string[]>,
sourceName: string,
desiredImplicitDeps: string[],
projectConfigurations: ProjectsConfigurations['projects'],
projectConfigurations: Record<string, ProjectConfiguration>,
projects: Record<string, ProjectGraphProjectNode>
) {
const invalidProjectsOrGlobs = desiredImplicitDeps.filter((implicit) => {
+11 -1
View File
@@ -37,12 +37,15 @@ import { retrieveProjectConfigurationsWithoutPluginInference } from '../project-
import { NxPluginV1 } from './nx-plugin.deprecated';
import { ProjectGraphDependencyWithFile } from '../project-graph/project-graph-builder';
import { combineGlobPatterns } from './globs';
import {
NxAngularJsonPlugin,
shouldMergeAngularProjects,
} from '../adapter/angular-json';
/**
* Context for {@link CreateNodesFunction}
*/
export interface CreateNodesContext {
readonly projectsConfigurations: Record<string, ProjectConfiguration>;
readonly nxJsonConfiguration: NxJsonConfiguration;
readonly workspaceRoot: string;
}
@@ -253,6 +256,10 @@ export async function loadNxPlugins(
jsPlugin.name = 'nx-js-graph-plugin';
result.push(jsPlugin as NxPlugin);
if (shouldMergeAngularProjects(root, false)) {
result.push(NxAngularJsonPlugin);
}
plugins ??= [];
for (const plugin of plugins) {
result.push(await loadNxPluginAsync(plugin, paths, root));
@@ -262,6 +269,9 @@ export async function loadNxPlugins(
}
function ensurePluginIsV2(plugin: NxPlugin): NxPluginV2 {
if (isNxPluginV2(plugin)) {
return plugin;
}
if (isNxPluginV1(plugin) && plugin.projectFilePatterns) {
return {
...plugin,
@@ -6,6 +6,7 @@ import {
PackageJson,
PackageJsonTargetConfiguration,
readModulePackageJson,
readTargetsFromPackageJson,
} from './package-json';
describe('buildTargetFromScript', () => {
@@ -44,6 +45,76 @@ describe('buildTargetFromScript', () => {
});
});
describe('readTargetsFromPackageJson', () => {
const packageJson: PackageJson = {
name: 'my-app',
version: '0.0.0',
scripts: {
build: 'echo 1',
},
};
const packageJsonBuildTarget = {
executor: 'nx:run-script',
options: {
script: 'build',
},
};
it('should read targets from project.json and package.json', () => {
const result = readTargetsFromPackageJson(packageJson);
expect(result).toEqual({
build: {
executor: 'nx:run-script',
options: {
script: 'build',
},
},
});
});
it('should contain extended options from nx property in package.json', () => {
const result = readTargetsFromPackageJson({
name: 'my-other-app',
version: '',
scripts: {
build: 'echo 1',
},
nx: {
targets: {
build: {
outputs: ['custom'],
},
},
},
});
expect(result).toEqual({
build: { ...packageJsonBuildTarget, outputs: ['custom'] },
});
});
it('should ignore scripts that are not in includedScripts', () => {
const result = readTargetsFromPackageJson({
name: 'included-scripts-test',
version: '',
scripts: {
test: 'echo testing',
fail: 'exit 1',
},
nx: {
includedScripts: ['test'],
},
});
expect(result).toEqual({
test: {
executor: 'nx:run-script',
options: { script: 'test' },
},
});
});
});
const rootPackageJson: PackageJson = readJsonFile(
join(workspaceRoot, 'package.json')
);

Some files were not shown because too many files have changed in this diff Show More