diff --git a/docs/angular/api-angular/generators/application.md b/docs/angular/api-angular/generators/application.md index 794b53958c..5c317fad44 100644 --- a/docs/angular/api-angular/generators/application.md +++ b/docs/angular/api-angular/generators/application.md @@ -128,6 +128,14 @@ Type: `boolean` Skip creating spec files. +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json + ### strict Default: `true` diff --git a/docs/angular/api-angular/generators/library.md b/docs/angular/api-angular/generators/library.md index c4987372f8..08495ee7c3 100644 --- a/docs/angular/api-angular/generators/library.md +++ b/docs/angular/api-angular/generators/library.md @@ -150,6 +150,14 @@ Type: `boolean` Do not update tsconfig.json for development experience. +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json + ### strict Default: `true` diff --git a/docs/angular/api-express/generators/application.md b/docs/angular/api-express/generators/application.md index fa4f5a0ae5..8e49680aa1 100644 --- a/docs/angular/api-express/generators/application.md +++ b/docs/angular/api-express/generators/application.md @@ -98,6 +98,14 @@ Type: `boolean` Do not add dependencies to package.json. +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json + ### tags Type: `string` diff --git a/docs/angular/api-gatsby/generators/application.md b/docs/angular/api-gatsby/generators/application.md index ed72dbe620..bb5cb750ab 100644 --- a/docs/angular/api-gatsby/generators/application.md +++ b/docs/angular/api-gatsby/generators/application.md @@ -66,6 +66,14 @@ Type: `boolean` Whether or not to configure the ESLint "parserOptions.project" option. We do not do this by default for lint performance reasons. +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json + ### style Alias(es): s diff --git a/docs/angular/api-nest/generators/application.md b/docs/angular/api-nest/generators/application.md index a8033843a3..67dedc1057 100644 --- a/docs/angular/api-nest/generators/application.md +++ b/docs/angular/api-nest/generators/application.md @@ -72,6 +72,14 @@ Type: `boolean` Do not add dependencies to package.json. +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json + ### tags Type: `string` diff --git a/docs/angular/api-next/generators/application.md b/docs/angular/api-next/generators/application.md index 292a04726d..669219336d 100644 --- a/docs/angular/api-next/generators/application.md +++ b/docs/angular/api-next/generators/application.md @@ -108,6 +108,14 @@ Type: `boolean` Skip updating workspace.json with default options based on values provided to this app (e.g. babel, style) +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json + ### style Alias(es): s diff --git a/docs/angular/api-node/generators/application.md b/docs/angular/api-node/generators/application.md index f187df290d..2d4718b74f 100644 --- a/docs/angular/api-node/generators/application.md +++ b/docs/angular/api-node/generators/application.md @@ -106,6 +106,14 @@ Type: `boolean` Do not add dependencies to package.json. +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json + ### tags Type: `string` diff --git a/docs/angular/api-node/generators/library.md b/docs/angular/api-node/generators/library.md index bffb37794f..f8b89989f7 100644 --- a/docs/angular/api-node/generators/library.md +++ b/docs/angular/api-node/generators/library.md @@ -138,6 +138,14 @@ Type: `boolean` Do not update tsconfig.base.json for development experience. +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json + ### strict Default: `false` diff --git a/docs/angular/api-nx-devkit/index.md b/docs/angular/api-nx-devkit/index.md index 22d4c4c357..4b5887a2d5 100644 --- a/docs/angular/api-nx-devkit/index.md +++ b/docs/angular/api-nx-devkit/index.md @@ -473,7 +473,7 @@ Callback to install dependencies only if necessary. undefined is returned if cha ### addProjectConfiguration -▸ **addProjectConfiguration**(`host`: [_Tree_](../../angular/nx-devkit/index#tree), `projectName`: _string_, `projectConfiguration`: [_ProjectConfiguration_](../../angular/nx-devkit/index#projectconfiguration) & [_NxJsonProjectConfiguration_](../../angular/nx-devkit/index#nxjsonprojectconfiguration)): _void_ +▸ **addProjectConfiguration**(`host`: [_Tree_](../../angular/nx-devkit/index#tree), `projectName`: _string_, `projectConfiguration`: [_ProjectConfiguration_](../../angular/nx-devkit/index#projectconfiguration) & [_NxJsonProjectConfiguration_](../../angular/nx-devkit/index#nxjsonprojectconfiguration), `standalone?`: _boolean_): _void_ Adds project configuration to the Nx workspace. @@ -482,11 +482,12 @@ both files. #### Parameters -| Name | Type | Description | -| :--------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------- | -| `host` | [_Tree_](../../angular/nx-devkit/index#tree) | the file system tree | -| `projectName` | _string_ | unique name. Often directories are part of the name (e.g., mydir-mylib) | -| `projectConfiguration` | [_ProjectConfiguration_](../../angular/nx-devkit/index#projectconfiguration) & [_NxJsonProjectConfiguration_](../../angular/nx-devkit/index#nxjsonprojectconfiguration) | project configuration | +| Name | Type | Default value | Description | +| :--------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------ | :---------------------------------------------------------------------- | +| `host` | [_Tree_](../../angular/nx-devkit/index#tree) | - | the file system tree | +| `projectName` | _string_ | - | unique name. Often directories are part of the name (e.g., mydir-mylib) | +| `projectConfiguration` | [_ProjectConfiguration_](../../angular/nx-devkit/index#projectconfiguration) & [_NxJsonProjectConfiguration_](../../angular/nx-devkit/index#nxjsonprojectconfiguration) | - | project configuration | +| `standalone` | _boolean_ | false | - | **Returns:** _void_ diff --git a/docs/angular/api-nx-plugin/generators/plugin.md b/docs/angular/api-nx-plugin/generators/plugin.md index 656fff8080..ea1023a339 100644 --- a/docs/angular/api-nx-plugin/generators/plugin.md +++ b/docs/angular/api-nx-plugin/generators/plugin.md @@ -78,6 +78,14 @@ Type: `boolean` Do not update tsconfig.json for development experience. +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json + ### tags Alias(es): t diff --git a/docs/angular/api-react/generators/application.md b/docs/angular/api-react/generators/application.md index 9845367f40..53173b4df3 100644 --- a/docs/angular/api-react/generators/application.md +++ b/docs/angular/api-react/generators/application.md @@ -150,6 +150,14 @@ Type: `boolean` Skip updating workspace.json with default options based on values provided to this app (e.g. babel, style). +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json + ### strict Default: `true` diff --git a/docs/angular/api-react/generators/library.md b/docs/angular/api-react/generators/library.md index a8ba5c65cb..629dd33712 100644 --- a/docs/angular/api-react/generators/library.md +++ b/docs/angular/api-react/generators/library.md @@ -158,6 +158,14 @@ Type: `boolean` Do not update tsconfig.json for development experience. +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json + ### strict Default: `true` diff --git a/docs/angular/api-react/generators/storybook-configuration.md b/docs/angular/api-react/generators/storybook-configuration.md index 0960cd8121..5def48cdd8 100644 --- a/docs/angular/api-react/generators/storybook-configuration.md +++ b/docs/angular/api-react/generators/storybook-configuration.md @@ -77,3 +77,11 @@ The tool to use for running lint checks. Type: `string` Project name + +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json diff --git a/docs/angular/api-storybook/generators/configuration.md b/docs/angular/api-storybook/generators/configuration.md index bbfd23285d..0b8b80da6c 100644 --- a/docs/angular/api-storybook/generators/configuration.md +++ b/docs/angular/api-storybook/generators/configuration.md @@ -60,6 +60,14 @@ Type: `string` Library or application name +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json + ### uiFramework Type: `string` diff --git a/docs/angular/api-storybook/generators/cypress-project.md b/docs/angular/api-storybook/generators/cypress-project.md index cb0211937b..5e75a55405 100644 --- a/docs/angular/api-storybook/generators/cypress-project.md +++ b/docs/angular/api-storybook/generators/cypress-project.md @@ -53,3 +53,11 @@ The tool to use for running lint checks. Type: `string` Library or application name + +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json diff --git a/docs/angular/api-web/generators/application.md b/docs/angular/api-web/generators/application.md index c7954c442c..d20d9fb8c1 100644 --- a/docs/angular/api-web/generators/application.md +++ b/docs/angular/api-web/generators/application.md @@ -76,6 +76,14 @@ Type: `boolean` Skip formatting files +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json + ### style Default: `css` diff --git a/docs/angular/api-workspace/generators/library.md b/docs/angular/api-workspace/generators/library.md index ff8fcffd4e..5ef0a69ab5 100644 --- a/docs/angular/api-workspace/generators/library.md +++ b/docs/angular/api-workspace/generators/library.md @@ -130,6 +130,14 @@ Type: `boolean` Do not update tsconfig.json for development experience. +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json + ### strict Default: `false` diff --git a/docs/node/api-angular/generators/application.md b/docs/node/api-angular/generators/application.md index 92ff2a5175..26b9fa5d6f 100644 --- a/docs/node/api-angular/generators/application.md +++ b/docs/node/api-angular/generators/application.md @@ -128,6 +128,14 @@ Type: `boolean` Skip creating spec files. +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json + ### strict Default: `true` diff --git a/docs/node/api-angular/generators/library.md b/docs/node/api-angular/generators/library.md index 617acfa27e..a2d8fa56f9 100644 --- a/docs/node/api-angular/generators/library.md +++ b/docs/node/api-angular/generators/library.md @@ -150,6 +150,14 @@ Type: `boolean` Do not update tsconfig.json for development experience. +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json + ### strict Default: `true` diff --git a/docs/node/api-express/generators/application.md b/docs/node/api-express/generators/application.md index c801960e6a..1f5b44281a 100644 --- a/docs/node/api-express/generators/application.md +++ b/docs/node/api-express/generators/application.md @@ -98,6 +98,14 @@ Type: `boolean` Do not add dependencies to package.json. +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json + ### tags Type: `string` diff --git a/docs/node/api-gatsby/generators/application.md b/docs/node/api-gatsby/generators/application.md index 7269ac5bbd..304a5932ed 100644 --- a/docs/node/api-gatsby/generators/application.md +++ b/docs/node/api-gatsby/generators/application.md @@ -66,6 +66,14 @@ Type: `boolean` Whether or not to configure the ESLint "parserOptions.project" option. We do not do this by default for lint performance reasons. +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json + ### style Alias(es): s diff --git a/docs/node/api-nest/generators/application.md b/docs/node/api-nest/generators/application.md index cca4d44354..bff9cfa98d 100644 --- a/docs/node/api-nest/generators/application.md +++ b/docs/node/api-nest/generators/application.md @@ -72,6 +72,14 @@ Type: `boolean` Do not add dependencies to package.json. +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json + ### tags Type: `string` diff --git a/docs/node/api-next/generators/application.md b/docs/node/api-next/generators/application.md index ebdc892804..7fdb044de5 100644 --- a/docs/node/api-next/generators/application.md +++ b/docs/node/api-next/generators/application.md @@ -108,6 +108,14 @@ Type: `boolean` Skip updating workspace.json with default options based on values provided to this app (e.g. babel, style) +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json + ### style Alias(es): s diff --git a/docs/node/api-node/generators/application.md b/docs/node/api-node/generators/application.md index 7978f16169..6da0095e63 100644 --- a/docs/node/api-node/generators/application.md +++ b/docs/node/api-node/generators/application.md @@ -106,6 +106,14 @@ Type: `boolean` Do not add dependencies to package.json. +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json + ### tags Type: `string` diff --git a/docs/node/api-node/generators/library.md b/docs/node/api-node/generators/library.md index 3097fb9634..317300e3be 100644 --- a/docs/node/api-node/generators/library.md +++ b/docs/node/api-node/generators/library.md @@ -138,6 +138,14 @@ Type: `boolean` Do not update tsconfig.base.json for development experience. +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json + ### strict Default: `false` diff --git a/docs/node/api-nx-devkit/index.md b/docs/node/api-nx-devkit/index.md index 4362e49c99..1b7a26f97f 100644 --- a/docs/node/api-nx-devkit/index.md +++ b/docs/node/api-nx-devkit/index.md @@ -473,7 +473,7 @@ Callback to install dependencies only if necessary. undefined is returned if cha ### addProjectConfiguration -▸ **addProjectConfiguration**(`host`: [_Tree_](../../node/nx-devkit/index#tree), `projectName`: _string_, `projectConfiguration`: [_ProjectConfiguration_](../../node/nx-devkit/index#projectconfiguration) & [_NxJsonProjectConfiguration_](../../node/nx-devkit/index#nxjsonprojectconfiguration)): _void_ +▸ **addProjectConfiguration**(`host`: [_Tree_](../../node/nx-devkit/index#tree), `projectName`: _string_, `projectConfiguration`: [_ProjectConfiguration_](../../node/nx-devkit/index#projectconfiguration) & [_NxJsonProjectConfiguration_](../../node/nx-devkit/index#nxjsonprojectconfiguration), `standalone?`: _boolean_): _void_ Adds project configuration to the Nx workspace. @@ -482,11 +482,12 @@ both files. #### Parameters -| Name | Type | Description | -| :--------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------- | -| `host` | [_Tree_](../../node/nx-devkit/index#tree) | the file system tree | -| `projectName` | _string_ | unique name. Often directories are part of the name (e.g., mydir-mylib) | -| `projectConfiguration` | [_ProjectConfiguration_](../../node/nx-devkit/index#projectconfiguration) & [_NxJsonProjectConfiguration_](../../node/nx-devkit/index#nxjsonprojectconfiguration) | project configuration | +| Name | Type | Default value | Description | +| :--------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------ | :---------------------------------------------------------------------- | +| `host` | [_Tree_](../../node/nx-devkit/index#tree) | - | the file system tree | +| `projectName` | _string_ | - | unique name. Often directories are part of the name (e.g., mydir-mylib) | +| `projectConfiguration` | [_ProjectConfiguration_](../../node/nx-devkit/index#projectconfiguration) & [_NxJsonProjectConfiguration_](../../node/nx-devkit/index#nxjsonprojectconfiguration) | - | project configuration | +| `standalone` | _boolean_ | false | - | **Returns:** _void_ diff --git a/docs/node/api-nx-plugin/generators/plugin.md b/docs/node/api-nx-plugin/generators/plugin.md index f7bce6c1ba..2934ff0176 100644 --- a/docs/node/api-nx-plugin/generators/plugin.md +++ b/docs/node/api-nx-plugin/generators/plugin.md @@ -78,6 +78,14 @@ Type: `boolean` Do not update tsconfig.json for development experience. +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json + ### tags Alias(es): t diff --git a/docs/node/api-react/generators/application.md b/docs/node/api-react/generators/application.md index 15bc5d395c..db9e3c7ab8 100644 --- a/docs/node/api-react/generators/application.md +++ b/docs/node/api-react/generators/application.md @@ -150,6 +150,14 @@ Type: `boolean` Skip updating workspace.json with default options based on values provided to this app (e.g. babel, style). +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json + ### strict Default: `true` diff --git a/docs/node/api-react/generators/library.md b/docs/node/api-react/generators/library.md index bd622569cf..d605dfc63a 100644 --- a/docs/node/api-react/generators/library.md +++ b/docs/node/api-react/generators/library.md @@ -158,6 +158,14 @@ Type: `boolean` Do not update tsconfig.json for development experience. +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json + ### strict Default: `true` diff --git a/docs/node/api-react/generators/storybook-configuration.md b/docs/node/api-react/generators/storybook-configuration.md index c013ff5a7d..06dc9ea29f 100644 --- a/docs/node/api-react/generators/storybook-configuration.md +++ b/docs/node/api-react/generators/storybook-configuration.md @@ -77,3 +77,11 @@ The tool to use for running lint checks. Type: `string` Project name + +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json diff --git a/docs/node/api-storybook/generators/configuration.md b/docs/node/api-storybook/generators/configuration.md index dffb1048ab..6e982ee587 100644 --- a/docs/node/api-storybook/generators/configuration.md +++ b/docs/node/api-storybook/generators/configuration.md @@ -60,6 +60,14 @@ Type: `string` Library or application name +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json + ### uiFramework Type: `string` diff --git a/docs/node/api-storybook/generators/cypress-project.md b/docs/node/api-storybook/generators/cypress-project.md index 5783c6d340..c3e27b2976 100644 --- a/docs/node/api-storybook/generators/cypress-project.md +++ b/docs/node/api-storybook/generators/cypress-project.md @@ -53,3 +53,11 @@ The tool to use for running lint checks. Type: `string` Library or application name + +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json diff --git a/docs/node/api-web/generators/application.md b/docs/node/api-web/generators/application.md index 914d89a1c5..e2595bf10f 100644 --- a/docs/node/api-web/generators/application.md +++ b/docs/node/api-web/generators/application.md @@ -76,6 +76,14 @@ Type: `boolean` Skip formatting files +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json + ### style Default: `css` diff --git a/docs/node/api-workspace/generators/library.md b/docs/node/api-workspace/generators/library.md index 44fd48eafb..1e2dd53851 100644 --- a/docs/node/api-workspace/generators/library.md +++ b/docs/node/api-workspace/generators/library.md @@ -130,6 +130,14 @@ Type: `boolean` Do not update tsconfig.json for development experience. +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json + ### strict Default: `false` diff --git a/docs/react/api-angular/generators/application.md b/docs/react/api-angular/generators/application.md index 92ff2a5175..26b9fa5d6f 100644 --- a/docs/react/api-angular/generators/application.md +++ b/docs/react/api-angular/generators/application.md @@ -128,6 +128,14 @@ Type: `boolean` Skip creating spec files. +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json + ### strict Default: `true` diff --git a/docs/react/api-angular/generators/library.md b/docs/react/api-angular/generators/library.md index 617acfa27e..a2d8fa56f9 100644 --- a/docs/react/api-angular/generators/library.md +++ b/docs/react/api-angular/generators/library.md @@ -150,6 +150,14 @@ Type: `boolean` Do not update tsconfig.json for development experience. +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json + ### strict Default: `true` diff --git a/docs/react/api-express/generators/application.md b/docs/react/api-express/generators/application.md index c801960e6a..1f5b44281a 100644 --- a/docs/react/api-express/generators/application.md +++ b/docs/react/api-express/generators/application.md @@ -98,6 +98,14 @@ Type: `boolean` Do not add dependencies to package.json. +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json + ### tags Type: `string` diff --git a/docs/react/api-gatsby/generators/application.md b/docs/react/api-gatsby/generators/application.md index 7269ac5bbd..304a5932ed 100644 --- a/docs/react/api-gatsby/generators/application.md +++ b/docs/react/api-gatsby/generators/application.md @@ -66,6 +66,14 @@ Type: `boolean` Whether or not to configure the ESLint "parserOptions.project" option. We do not do this by default for lint performance reasons. +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json + ### style Alias(es): s diff --git a/docs/react/api-nest/generators/application.md b/docs/react/api-nest/generators/application.md index cca4d44354..bff9cfa98d 100644 --- a/docs/react/api-nest/generators/application.md +++ b/docs/react/api-nest/generators/application.md @@ -72,6 +72,14 @@ Type: `boolean` Do not add dependencies to package.json. +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json + ### tags Type: `string` diff --git a/docs/react/api-next/generators/application.md b/docs/react/api-next/generators/application.md index ebdc892804..7fdb044de5 100644 --- a/docs/react/api-next/generators/application.md +++ b/docs/react/api-next/generators/application.md @@ -108,6 +108,14 @@ Type: `boolean` Skip updating workspace.json with default options based on values provided to this app (e.g. babel, style) +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json + ### style Alias(es): s diff --git a/docs/react/api-node/generators/application.md b/docs/react/api-node/generators/application.md index 7978f16169..6da0095e63 100644 --- a/docs/react/api-node/generators/application.md +++ b/docs/react/api-node/generators/application.md @@ -106,6 +106,14 @@ Type: `boolean` Do not add dependencies to package.json. +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json + ### tags Type: `string` diff --git a/docs/react/api-node/generators/library.md b/docs/react/api-node/generators/library.md index 3097fb9634..317300e3be 100644 --- a/docs/react/api-node/generators/library.md +++ b/docs/react/api-node/generators/library.md @@ -138,6 +138,14 @@ Type: `boolean` Do not update tsconfig.base.json for development experience. +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json + ### strict Default: `false` diff --git a/docs/react/api-nx-devkit/index.md b/docs/react/api-nx-devkit/index.md index 9a38cb6e71..98381ea69f 100644 --- a/docs/react/api-nx-devkit/index.md +++ b/docs/react/api-nx-devkit/index.md @@ -473,7 +473,7 @@ Callback to install dependencies only if necessary. undefined is returned if cha ### addProjectConfiguration -▸ **addProjectConfiguration**(`host`: [_Tree_](../../react/nx-devkit/index#tree), `projectName`: _string_, `projectConfiguration`: [_ProjectConfiguration_](../../react/nx-devkit/index#projectconfiguration) & [_NxJsonProjectConfiguration_](../../react/nx-devkit/index#nxjsonprojectconfiguration)): _void_ +▸ **addProjectConfiguration**(`host`: [_Tree_](../../react/nx-devkit/index#tree), `projectName`: _string_, `projectConfiguration`: [_ProjectConfiguration_](../../react/nx-devkit/index#projectconfiguration) & [_NxJsonProjectConfiguration_](../../react/nx-devkit/index#nxjsonprojectconfiguration), `standalone?`: _boolean_): _void_ Adds project configuration to the Nx workspace. @@ -482,11 +482,12 @@ both files. #### Parameters -| Name | Type | Description | -| :--------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :---------------------------------------------------------------------- | -| `host` | [_Tree_](../../react/nx-devkit/index#tree) | the file system tree | -| `projectName` | _string_ | unique name. Often directories are part of the name (e.g., mydir-mylib) | -| `projectConfiguration` | [_ProjectConfiguration_](../../react/nx-devkit/index#projectconfiguration) & [_NxJsonProjectConfiguration_](../../react/nx-devkit/index#nxjsonprojectconfiguration) | project configuration | +| Name | Type | Default value | Description | +| :--------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------------ | :---------------------------------------------------------------------- | +| `host` | [_Tree_](../../react/nx-devkit/index#tree) | - | the file system tree | +| `projectName` | _string_ | - | unique name. Often directories are part of the name (e.g., mydir-mylib) | +| `projectConfiguration` | [_ProjectConfiguration_](../../react/nx-devkit/index#projectconfiguration) & [_NxJsonProjectConfiguration_](../../react/nx-devkit/index#nxjsonprojectconfiguration) | - | project configuration | +| `standalone` | _boolean_ | false | - | **Returns:** _void_ diff --git a/docs/react/api-nx-plugin/generators/plugin.md b/docs/react/api-nx-plugin/generators/plugin.md index f7bce6c1ba..2934ff0176 100644 --- a/docs/react/api-nx-plugin/generators/plugin.md +++ b/docs/react/api-nx-plugin/generators/plugin.md @@ -78,6 +78,14 @@ Type: `boolean` Do not update tsconfig.json for development experience. +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json + ### tags Alias(es): t diff --git a/docs/react/api-react/generators/application.md b/docs/react/api-react/generators/application.md index 15bc5d395c..db9e3c7ab8 100644 --- a/docs/react/api-react/generators/application.md +++ b/docs/react/api-react/generators/application.md @@ -150,6 +150,14 @@ Type: `boolean` Skip updating workspace.json with default options based on values provided to this app (e.g. babel, style). +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json + ### strict Default: `true` diff --git a/docs/react/api-react/generators/library.md b/docs/react/api-react/generators/library.md index bd622569cf..d605dfc63a 100644 --- a/docs/react/api-react/generators/library.md +++ b/docs/react/api-react/generators/library.md @@ -158,6 +158,14 @@ Type: `boolean` Do not update tsconfig.json for development experience. +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json + ### strict Default: `true` diff --git a/docs/react/api-react/generators/storybook-configuration.md b/docs/react/api-react/generators/storybook-configuration.md index c013ff5a7d..06dc9ea29f 100644 --- a/docs/react/api-react/generators/storybook-configuration.md +++ b/docs/react/api-react/generators/storybook-configuration.md @@ -77,3 +77,11 @@ The tool to use for running lint checks. Type: `string` Project name + +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json diff --git a/docs/react/api-storybook/generators/configuration.md b/docs/react/api-storybook/generators/configuration.md index dffb1048ab..6e982ee587 100644 --- a/docs/react/api-storybook/generators/configuration.md +++ b/docs/react/api-storybook/generators/configuration.md @@ -60,6 +60,14 @@ Type: `string` Library or application name +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json + ### uiFramework Type: `string` diff --git a/docs/react/api-storybook/generators/cypress-project.md b/docs/react/api-storybook/generators/cypress-project.md index 5783c6d340..c3e27b2976 100644 --- a/docs/react/api-storybook/generators/cypress-project.md +++ b/docs/react/api-storybook/generators/cypress-project.md @@ -53,3 +53,11 @@ The tool to use for running lint checks. Type: `string` Library or application name + +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json diff --git a/docs/react/api-web/generators/application.md b/docs/react/api-web/generators/application.md index 914d89a1c5..e2595bf10f 100644 --- a/docs/react/api-web/generators/application.md +++ b/docs/react/api-web/generators/application.md @@ -76,6 +76,14 @@ Type: `boolean` Skip formatting files +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json + ### style Default: `css` diff --git a/docs/react/api-workspace/generators/library.md b/docs/react/api-workspace/generators/library.md index 44fd48eafb..1e2dd53851 100644 --- a/docs/react/api-workspace/generators/library.md +++ b/docs/react/api-workspace/generators/library.md @@ -130,6 +130,14 @@ Type: `boolean` Do not update tsconfig.json for development experience. +### standaloneConfig + +Default: `false` + +Type: `boolean` + +Split the project configuration into /project.json rather than including it inside workspace.json + ### strict Default: `false` diff --git a/e2e/node/src/node.test.ts b/e2e/node/src/node.test.ts index eca838f14c..3020b0fcbb 100644 --- a/e2e/node/src/node.test.ts +++ b/e2e/node/src/node.test.ts @@ -56,6 +56,24 @@ describe('Node Applications', () => { expect(result).toContain('Hello World!'); }, 300000); + // TODO: This test fails in CI, but succeeds locally. It should be re-enabled once the reasoning is understood. + xit('should be able to generate an empty application with standalone configuration', async () => { + const nodeapp = uniq('nodeapp'); + + runCLI( + `generate @nrwl/node:app ${nodeapp} --linter=eslint --standaloneConfig` + ); + + updateFile(`apps/${nodeapp}/src/main.ts`, `console.log('Hello World!');`); + await runCLIAsync(`build ${nodeapp}`); + + checkFilesExist(`dist/apps/${nodeapp}/main.js`); + const result = execSync(`node dist/apps/${nodeapp}/main.js`, { + cwd: tmpProjPath(), + }).toString(); + expect(result).toContain('Hello World!'); + }, 300000); + xit('should be able to generate an express application', async () => { const nodeapp = uniq('nodeapp'); const port = 3334; diff --git a/e2e/utils/index.ts b/e2e/utils/index.ts index 92cd09ae0b..9244f9eba3 100644 --- a/e2e/utils/index.ts +++ b/e2e/utils/index.ts @@ -1,4 +1,5 @@ import { detectPackageManager } from '@nrwl/tao/src/shared/package-manager'; +import { inlineProjectConfigurations } from '@nrwl/tao/src/shared/workspace'; import { ChildProcess, exec, execSync } from 'child_process'; import { copySync, @@ -19,6 +20,7 @@ const kill = require('kill-port'); const isWindows = require('is-windows'); import { check as portCheck } from 'tcp-port-used'; import { parseJson } from '@nrwl/devkit'; + import chalk = require('chalk'); import treeKill = require('tree-kill'); import { promisify } from 'util'; @@ -36,7 +38,7 @@ interface RunCmdOpts { } export function currentCli() { - return process.env.SELECTED_CLI ?? 'nx'; + return process.env.SELECTED_CLI || 'nx'; } export function isNightlyRun() { @@ -443,7 +445,10 @@ function setMaxWorkers() { const workspace = readJson(workspaceFile); Object.keys(workspace.projects).forEach((appName) => { - const project = workspace.projects[appName]; + let project = workspace.projects[appName]; + if (typeof project === 'string') { + project = readJson(path.join(project, 'project.json')); + } const { build } = project.targets ?? project.architect; if (!build) { diff --git a/e2e/web/src/file-server.test.ts b/e2e/web/src/file-server.test.ts index 7f9df94e81..42ea79f5b4 100644 --- a/e2e/web/src/file-server.test.ts +++ b/e2e/web/src/file-server.test.ts @@ -38,5 +38,5 @@ describe('file-server', () => { } catch { expect('process running').toBeFalsy(); } - }, 300000); + }, 150000); }); diff --git a/e2e/web/src/web.test.ts b/e2e/web/src/web.test.ts index 96baabdf4c..4157917a2e 100644 --- a/e2e/web/src/web.test.ts +++ b/e2e/web/src/web.test.ts @@ -21,6 +21,8 @@ describe('Web Components Applications', () => { const appName = uniq('app'); runCLI(`generate @nrwl/web:app ${appName} --no-interactive`); + checkFilesDoNotExist(`apps/${appName}/project.json`); + const lintResults = runCLI(`lint ${appName}`); expect(lintResults).toContain('All files pass linting.'); @@ -60,6 +62,18 @@ describe('Web Components Applications', () => { } }, 500000); + it('should be able to generate a web app with standaloneConfig', async () => { + const appName = uniq('app'); + runCLI( + `generate @nrwl/web:app ${appName} --no-interactive --standalone-config` + ); + + checkFilesExist(`apps/${appName}/project.json`); + + const lintResults = runCLI(`lint ${appName}`); + expect(lintResults).toContain('All files pass linting.'); + }, 120000); + it('should remove previous output before building', async () => { const appName = uniq('app'); const libName = uniq('lib'); diff --git a/packages/angular/src/generators/application/application.ts b/packages/angular/src/generators/application/application.ts index b9da40553d..50a37aec4c 100644 --- a/packages/angular/src/generators/application/application.ts +++ b/packages/angular/src/generators/application/application.ts @@ -9,6 +9,7 @@ import { installPackagesTask, } from '@nrwl/devkit'; import { wrapAngularDevkitSchematic } from '@nrwl/devkit/ngcli-adapter'; +import { convertToNxProjectGenerator } from '@nrwl/workspace'; import { UnitTestRunner } from '../../utils/test-runners'; import init from '../init/init'; @@ -99,6 +100,10 @@ export async function applicationGenerator( setApplicationStrictDefault(host, false); } + if (options.standaloneConfig) { + await convertToNxProjectGenerator(host, {project: options.name, all: false}); + } + if (!options.skipFormat) { await formatFiles(host); } diff --git a/packages/angular/src/generators/application/schema.d.ts b/packages/angular/src/generators/application/schema.d.ts index 334e6fc13d..2e463c8c45 100644 --- a/packages/angular/src/generators/application/schema.d.ts +++ b/packages/angular/src/generators/application/schema.d.ts @@ -19,4 +19,5 @@ export interface Schema { e2eTestRunner: E2eTestRunner; backendProject?: string; strict?: boolean; + standaloneConfig?: boolean; } diff --git a/packages/angular/src/generators/application/schema.json b/packages/angular/src/generators/application/schema.json index 4c50fc9508..aac8aa6e40 100644 --- a/packages/angular/src/generators/application/schema.json +++ b/packages/angular/src/generators/application/schema.json @@ -117,6 +117,11 @@ "type": "boolean", "description": "Creates an application with stricter type checking and build optimization options.", "default": true + }, + "standaloneConfig": { + "description": "Split the project configuration into /project.json rather than including it inside workspace.json", + "type": "boolean", + "default": false } }, "required": [] diff --git a/packages/angular/src/generators/library/schema.d.ts b/packages/angular/src/generators/library/schema.d.ts index 6d9ea8713b..e7635d7864 100644 --- a/packages/angular/src/generators/library/schema.d.ts +++ b/packages/angular/src/generators/library/schema.d.ts @@ -11,6 +11,7 @@ export interface Schema { buildable: boolean; publishable: boolean; importPath?: string; + standaloneConfig?: boolean; spec?: boolean; flat?: boolean; diff --git a/packages/angular/src/generators/library/schema.json b/packages/angular/src/generators/library/schema.json index f124026416..c389335f9e 100644 --- a/packages/angular/src/generators/library/schema.json +++ b/packages/angular/src/generators/library/schema.json @@ -103,6 +103,11 @@ "description": "Enable Ivy for library in tsconfig.lib.prod.json. Should not be used with publishable libraries.", "type": "boolean", "default": false + }, + "standaloneConfig": { + "description": "Split the project configuration into /project.json rather than including it inside workspace.json", + "type": "boolean", + "default": false } }, "required": [] diff --git a/packages/cypress/src/generators/cypress-project/cypress-project.spec.ts b/packages/cypress/src/generators/cypress-project/cypress-project.spec.ts index 6cff96a2d3..542217a10a 100644 --- a/packages/cypress/src/generators/cypress-project/cypress-project.spec.ts +++ b/packages/cypress/src/generators/cypress-project/cypress-project.spec.ts @@ -14,6 +14,7 @@ describe('schematic:cypress-project', () => { let tree: Tree; const defaultOptions: Omit = { linter: Linter.EsLint, + standaloneConfig: false, }; beforeEach(() => { @@ -76,6 +77,7 @@ describe('schematic:cypress-project', () => { name: 'my-app-e2e', project: 'my-app', linter: Linter.TsLint, + standaloneConfig: false, }); const workspaceJson = readJson(tree, 'workspace.json'); const project = workspaceJson.projects['my-app-e2e']; @@ -114,6 +116,7 @@ describe('schematic:cypress-project', () => { name: 'my-app-e2e', project: 'my-app', linter: Linter.TsLint, + standaloneConfig: false, }); const workspaceJson = readJson(tree, 'workspace.json'); const project = workspaceJson.projects['my-app-e2e']; @@ -147,6 +150,7 @@ describe('schematic:cypress-project', () => { name: 'my-app-e2e', project: 'my-app', linter: Linter.EsLint, + standaloneConfig: false, }); const workspaceJson = readJson(tree, 'workspace.json'); const project = workspaceJson.projects['my-app-e2e']; @@ -164,6 +168,7 @@ describe('schematic:cypress-project', () => { name: 'my-app-e2e', project: 'my-app', linter: Linter.None, + standaloneConfig: false, }); const workspaceJson = readJson(tree, 'workspace.json'); const project = workspaceJson.projects['my-app-e2e']; @@ -176,6 +181,7 @@ describe('schematic:cypress-project', () => { name: 'my-app-e2e', project: 'my-app', linter: Linter.EsLint, + standaloneConfig: false, }); const project = readProjectConfiguration(tree, 'my-app-e2e'); @@ -224,6 +230,7 @@ describe('schematic:cypress-project', () => { project: 'my-dir-my-app', directory: 'my-dir', linter: Linter.TsLint, + standaloneConfig: false, }); const projectConfig = readJson(tree, 'workspace.json').projects[ 'my-dir-my-app-e2e' @@ -319,6 +326,7 @@ describe('schematic:cypress-project', () => { name: 'my-app-e2e', project: 'my-app', linter: Linter.EsLint, + standaloneConfig: false, }); const packageJson = readJson(tree, 'package.json'); expect( diff --git a/packages/cypress/src/generators/cypress-project/cypress-project.ts b/packages/cypress/src/generators/cypress-project/cypress-project.ts index 903dabe2b4..dadc5e9172 100644 --- a/packages/cypress/src/generators/cypress-project/cypress-project.ts +++ b/packages/cypress/src/generators/cypress-project/cypress-project.ts @@ -51,28 +51,39 @@ function addProject(tree: Tree, options: CypressProjectSchema) { : devServerTarget; } - addProjectConfiguration(tree, options.projectName, { - root: options.projectRoot, - sourceRoot: joinPathFragments(options.projectRoot, 'src'), - projectType: 'application', - targets: { - e2e: { - executor: '@nrwl/cypress:cypress', - options: { - cypressConfig: joinPathFragments(options.projectRoot, 'cypress.json'), - tsConfig: joinPathFragments(options.projectRoot, 'tsconfig.e2e.json'), - devServerTarget, - }, - configurations: { - production: { - devServerTarget: `${options.project}:serve:production`, + addProjectConfiguration( + tree, + options.projectName, + { + root: options.projectRoot, + sourceRoot: joinPathFragments(options.projectRoot, 'src'), + projectType: 'application', + targets: { + e2e: { + executor: '@nrwl/cypress:cypress', + options: { + cypressConfig: joinPathFragments( + options.projectRoot, + 'cypress.json' + ), + tsConfig: joinPathFragments( + options.projectRoot, + 'tsconfig.e2e.json' + ), + devServerTarget, + }, + configurations: { + production: { + devServerTarget: `${options.project}:serve:production`, + }, }, }, }, + tags: [], + implicitDependencies: options.project ? [options.project] : undefined, }, - tags: [], - implicitDependencies: options.project ? [options.project] : undefined, - }); + options.standaloneConfig + ); } export async function addLinter(host: Tree, options: CypressProjectSchema) { diff --git a/packages/cypress/src/generators/cypress-project/schema.d.ts b/packages/cypress/src/generators/cypress-project/schema.d.ts index fdff50b834..4cd51d74ee 100644 --- a/packages/cypress/src/generators/cypress-project/schema.d.ts +++ b/packages/cypress/src/generators/cypress-project/schema.d.ts @@ -8,4 +8,5 @@ export interface Schema { js?: boolean; skipFormat?: boolean; setParserOptionsProject?: boolean; + standaloneConfig?: booleann; } diff --git a/packages/cypress/src/generators/cypress-project/schema.json b/packages/cypress/src/generators/cypress-project/schema.json index 46d6645949..f753ef78c2 100644 --- a/packages/cypress/src/generators/cypress-project/schema.json +++ b/packages/cypress/src/generators/cypress-project/schema.json @@ -45,6 +45,11 @@ "type": "boolean", "description": "Whether or not to configure the ESLint \"parserOptions.project\" option. We do not do this by default for lint performance reasons.", "default": false + }, + "standaloneConfig": { + "description": "Split the project configuration into /project.json rather than including it inside workspace.json", + "type": "boolean", + "default": false } }, "required": ["name"] diff --git a/packages/devkit/src/generators/project-configuration.spec.ts b/packages/devkit/src/generators/project-configuration.spec.ts new file mode 100644 index 0000000000..5f6ba6e978 --- /dev/null +++ b/packages/devkit/src/generators/project-configuration.spec.ts @@ -0,0 +1,101 @@ +import { Tree } from '@nrwl/tao/src/shared/tree'; +import { + readProjectConfiguration, + addProjectConfiguration, + updateProjectConfiguration, + removeProjectConfiguration, + getProjects, +} from './project-configuration'; +import { createTreeWithEmptyWorkspace } from '../tests/create-tree-with-empty-workspace'; +import { ProjectConfiguration } from '@nrwl/tao/src/shared/workspace'; +import { readJson } from '../utils/json'; + +const baseTestProjectConfig: ProjectConfiguration = { + root: 'libs/test', + sourceRoot: 'libs/test/src', + targets: {}, +}; + +describe('project configuration', () => { + let tree: Tree; + + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(); + }); + + it('should create project.json file when adding a project if standalone is true', () => { + addProjectConfiguration(tree, 'test', baseTestProjectConfig, true); + + expect(tree.exists('libs/test/project.json')).toBeTruthy(); + }); + + it('should not create project.json file when adding a project if standalone is false', () => { + addProjectConfiguration(tree, 'test', baseTestProjectConfig, false); + + expect(tree.exists('libs/test/project.json')).toBeFalsy(); + }); + + it('should be able to read from standalone projects', () => { + tree.write( + 'libs/test/project.json', + JSON.stringify(baseTestProjectConfig, null, 2) + ); + tree.write( + 'workspace.json', + JSON.stringify( + { + projects: { + test: 'libs/test', + }, + }, + null, + 2 + ) + ); + + const projectConfig = readProjectConfiguration(tree, 'test'); + + expect(projectConfig).toEqual(baseTestProjectConfig); + }); + + it('should update project.json file when updating a project', () => { + addProjectConfiguration(tree, 'test', baseTestProjectConfig, true); + const expectedProjectConfig = { + ...baseTestProjectConfig, + targets: { build: { executor: '' } }, + }; + updateProjectConfiguration(tree, 'test', expectedProjectConfig); + + expect(readJson(tree, 'libs/test/project.json')).toEqual( + expectedProjectConfig + ); + }); + + it('should update workspace.json file when updating an inline project', () => { + addProjectConfiguration(tree, 'test', baseTestProjectConfig, false); + const expectedProjectConfig = { + ...baseTestProjectConfig, + targets: { build: { executor: '' } }, + }; + updateProjectConfiguration(tree, 'test', expectedProjectConfig); + + expect(readJson(tree, 'workspace.json').projects.test).toEqual( + expectedProjectConfig + ); + }); + + it('should remove project.json file when removing projct configuration', () => { + addProjectConfiguration(tree, 'test', baseTestProjectConfig, true); + removeProjectConfiguration(tree, 'test'); + + expect(tree.exists('test/project.json')).toBeFalsy(); + }); + + it('should support workspaces with standalone and inline projects', () => { + addProjectConfiguration(tree, 'test', baseTestProjectConfig, true); + addProjectConfiguration(tree, 'test2', baseTestProjectConfig, false); + const configurations = getProjects(tree); + expect(configurations.get('test')).toEqual(baseTestProjectConfig); + expect(configurations.get('test2')).toEqual(baseTestProjectConfig); + }); +}); diff --git a/packages/devkit/src/generators/project-configuration.ts b/packages/devkit/src/generators/project-configuration.ts index 1b95c11bed..703494233d 100644 --- a/packages/devkit/src/generators/project-configuration.ts +++ b/packages/devkit/src/generators/project-configuration.ts @@ -1,15 +1,17 @@ import type { Tree } from '@nrwl/tao/src/shared/tree'; -import type { +import { ProjectConfiguration, + RawWorkspaceJsonConfiguration, + toNewFormat, WorkspaceJsonConfiguration, } from '@nrwl/tao/src/shared/workspace'; -import { toNewFormat } from '@nrwl/tao/src/shared/workspace'; import { readJson, updateJson, writeJson } from '../utils/json'; import type { NxJsonConfiguration, NxJsonProjectConfiguration, } from '@nrwl/tao/src/shared/nx'; import { getWorkspacePath } from '../utils/get-workspace-layout'; +import { join } from 'path'; export type WorkspaceConfiguration = Omit< WorkspaceJsonConfiguration, @@ -30,9 +32,16 @@ export type WorkspaceConfiguration = Omit< export function addProjectConfiguration( host: Tree, projectName: string, - projectConfiguration: ProjectConfiguration & NxJsonProjectConfiguration + projectConfiguration: ProjectConfiguration & NxJsonProjectConfiguration, + standalone: boolean = false ): void { - setProjectConfiguration(host, projectName, projectConfiguration, 'create'); + setProjectConfiguration( + host, + projectName, + projectConfiguration, + 'create', + standalone + ); } /** @@ -78,10 +87,10 @@ export function getProjects( const nxJson = readJson(host, 'nx.json'); return new Map( - Object.keys(workspace.projects).map((projectName) => { + Object.keys(workspace.projects || {}).map((projectName) => { return [ projectName, - getProjectConfiguration(projectName, workspace, nxJson), + getProjectConfiguration(host, projectName, workspace, nxJson), ]; }) ); @@ -156,31 +165,36 @@ export function readProjectConfiguration( } const nxJson = readJson(host, 'nx.json'); - if (!nxJson.projects[projectName]) { - throw new Error( - `Cannot find configuration for '${projectName}' in nx.json` - ); - } - return getProjectConfiguration(projectName, workspace, nxJson); + // TODO: Remove after confirming that nx.json should be optional. + // if (!nxJson.projects[projectName]) { + // throw new Error( + // `Cannot find configuration for '${projectName}' in nx.json` + // ); + // } + + return getProjectConfiguration(host, projectName, workspace, nxJson); } function getProjectConfiguration( + host: Tree, projectName: string, workspace: WorkspaceJsonConfiguration, nxJson: NxJsonConfiguration ): ProjectConfiguration & NxJsonProjectConfiguration { return { - ...readWorkspaceSection(workspace, projectName), + ...readWorkspaceSection(host, workspace, projectName), ...readNxJsonSection(nxJson, projectName), }; } function readWorkspaceSection( + host: Tree, workspace: WorkspaceJsonConfiguration, projectName: string ) { - return workspace.projects[projectName]; + const config = workspace.projects[projectName]; + return config; } function readNxJsonSection(nxJson: NxJsonConfiguration, projectName: string) { @@ -191,7 +205,8 @@ function setProjectConfiguration( host: Tree, projectName: string, projectConfiguration: ProjectConfiguration & NxJsonProjectConfiguration, - mode: 'create' | 'update' | 'delete' + mode: 'create' | 'update' | 'delete', + standalone: boolean = false ) { if (mode === 'delete') { addProjectToNxJson(host, projectName, undefined, mode); @@ -205,9 +220,14 @@ function setProjectConfiguration( ); } - const { tags, implicitDependencies, ...workspaceConfiguration } = - projectConfiguration; - addProjectToWorkspaceJson(host, projectName, workspaceConfiguration, mode); + const { tags, implicitDependencies } = projectConfiguration; + addProjectToWorkspaceJson( + host, + projectName, + projectConfiguration, + mode, + standalone + ); addProjectToNxJson( host, projectName, @@ -222,11 +242,118 @@ function setProjectConfiguration( function addProjectToWorkspaceJson( host: Tree, projectName: string, - project: ProjectConfiguration, - mode: 'create' | 'update' | 'delete' + project: ProjectConfiguration & NxJsonProjectConfiguration, + mode: 'create' | 'update' | 'delete', + standalone: boolean = false ) { const path = getWorkspacePath(host); - const workspaceJson = readJson(host, path); + const workspaceJson = readJson(host, path); + + validateWorkspaceJsonOperations(mode, workspaceJson, projectName); + + const configFile = + mode === 'create' && standalone + ? join(project.root, 'project.json') + : getProjectFileLocation(host, projectName); + + if (configFile) { + if (mode === 'delete') { + host.delete(configFile); + } else { + writeJson(host, configFile, project); + } + + if (mode === 'create') { + workspaceJson.projects[projectName] = project.root; + writeJson(host, path, workspaceJson); + } + } else { + let workspaceConfiguration: ProjectConfiguration; + if (project) { + const { tags, implicitDependencies, ...c } = project; + workspaceConfiguration = c; + } + + workspaceJson.projects[projectName] = workspaceConfiguration; + writeJson(host, path, workspaceJson); + } +} + +function addProjectToNxJson( + host: Tree, + projectName: string, + config: NxJsonProjectConfiguration, + mode: 'create' | 'update' | 'delete' +) { + // distributed project files do not use nx.json, + // so only proceed if the project does not use them. + if (!getProjectFileLocation(host, projectName)) { + const nxJson = readJson(host, 'nx.json'); + if (mode === 'delete') { + delete nxJson.projects[projectName]; + } else { + nxJson.projects[projectName] = { + ...{ + tags: [], + }, + ...(config || {}), + }; + } + writeJson(host, 'nx.json', nxJson); + } +} + +function readWorkspace(host: Tree): WorkspaceJsonConfiguration { + const workspaceJson = inlineProjectConfigurationsWithTree(host); + const originalVersion = workspaceJson.version; + return { + ...toNewFormat(workspaceJson), + version: originalVersion, + }; +} + +/** + * This has to be separate from the inline functionality inside tao, + * as the functionality in tao does not use a Tree. Changes made during + * a generator would not be present during runtime execution. + * @returns + */ +function inlineProjectConfigurationsWithTree( + host: Tree +): WorkspaceJsonConfiguration { + const path = getWorkspacePath(host); + const workspaceJson = readJson(host, path); + Object.entries(workspaceJson.projects || {}).forEach(([project, config]) => { + if (typeof config === 'string') { + const configFileLocation = join(config, 'project.json'); + workspaceJson.projects[project] = readJson< + ProjectConfiguration & NxJsonProjectConfiguration + >(host, configFileLocation); + } + }); + return workspaceJson as WorkspaceJsonConfiguration; +} + +/** + * @description Determine where a project's configuration is located. + * @returns file path if separate from root config, null otherwise. + */ +function getProjectFileLocation(host: Tree, project: string): string | null { + const rawWorkspace = readJson( + host, + getWorkspacePath(host) + ); + const projectConfig = rawWorkspace.projects?.[project]; + return typeof projectConfig === 'string' + ? join(projectConfig, 'project.json') + : null; +} + +function validateWorkspaceJsonOperations( + mode: 'create' | 'update' | 'delete', + workspaceJson: RawWorkspaceJsonConfiguration | WorkspaceJsonConfiguration, + projectName: string +) { if (mode == 'create' && workspaceJson.projects[projectName]) { throw new Error( `Cannot create Project '${projectName}'. It already exists.` @@ -239,39 +366,7 @@ function addProjectToWorkspaceJson( } if (mode == 'delete' && !workspaceJson.projects[projectName]) { throw new Error( - `Cannot update Project '${projectName}'. It does not exist.` + `Cannot delete Project '${projectName}'. It does not exist.` ); } - workspaceJson.projects[projectName] = project; - writeJson(host, path, workspaceJson); -} - -function addProjectToNxJson( - host: Tree, - projectName: string, - config: NxJsonProjectConfiguration, - mode: 'create' | 'update' | 'delete' -) { - const nxJson = readJson(host, 'nx.json'); - if (mode === 'delete') { - delete nxJson.projects[projectName]; - } else { - nxJson.projects[projectName] = { - ...{ - tags: [], - }, - ...(config || {}), - }; - } - writeJson(host, 'nx.json', nxJson); -} - -function readWorkspace(host: Tree): WorkspaceJsonConfiguration { - const path = getWorkspacePath(host); - const workspaceJson = readJson(host, path); - const originalVersion = workspaceJson.version; - return { - ...toNewFormat(workspaceJson), - version: originalVersion, - }; } diff --git a/packages/devkit/src/tests/create-tree-with-empty-workspace.ts b/packages/devkit/src/tests/create-tree-with-empty-workspace.ts index 1a3ccefac3..00a8ddfa7a 100644 --- a/packages/devkit/src/tests/create-tree-with-empty-workspace.ts +++ b/packages/devkit/src/tests/create-tree-with-empty-workspace.ts @@ -4,10 +4,10 @@ import type { Tree } from '@nrwl/tao/src/shared/tree'; /** * Creates a host for testing. */ -export function createTreeWithEmptyWorkspace(): Tree { +export function createTreeWithEmptyWorkspace(version = 1): Tree { const tree = new FsTree('/virtual', false); - tree.write('/workspace.json', JSON.stringify({ version: 1, projects: {} })); + tree.write('/workspace.json', JSON.stringify({ version, projects: {} })); tree.write('./.prettierrc', JSON.stringify({ singleQuote: true })); tree.write( '/package.json', diff --git a/packages/express/src/generators/application/schema.d.ts b/packages/express/src/generators/application/schema.d.ts index abbaca1a6a..c0c0327423 100644 --- a/packages/express/src/generators/application/schema.d.ts +++ b/packages/express/src/generators/application/schema.d.ts @@ -13,4 +13,5 @@ export interface Schema { babelJest?: boolean; js: boolean; pascalCaseFiles: boolean; + standaloneConfig?: boolean; } diff --git a/packages/express/src/generators/application/schema.json b/packages/express/src/generators/application/schema.json index 72c9ffbb99..f6165b8328 100644 --- a/packages/express/src/generators/application/schema.json +++ b/packages/express/src/generators/application/schema.json @@ -64,6 +64,11 @@ "type": "boolean", "description": "Generate JavaScript files rather than TypeScript files.", "default": false + }, + "standaloneConfig": { + "description": "Split the project configuration into /project.json rather than including it inside workspace.json", + "type": "boolean", + "default": false } }, "required": [] diff --git a/packages/gatsby/src/generators/application/application.spec.ts b/packages/gatsby/src/generators/application/application.spec.ts index 5c60427dd4..fe9309b45c 100644 --- a/packages/gatsby/src/generators/application/application.spec.ts +++ b/packages/gatsby/src/generators/application/application.spec.ts @@ -13,7 +13,11 @@ describe('app', () => { }); it('should update workspace.json', async () => { - await applicationGenerator(tree, { name: 'myApp', style: 'css' }); + await applicationGenerator(tree, { + name: 'myApp', + style: 'css', + standaloneConfig: false, + }); const workspaceJson = readJson(tree, '/workspace.json'); expect(workspaceJson.projects['my-app'].root).toEqual('apps/my-app'); @@ -28,6 +32,7 @@ describe('app', () => { name: 'myApp', style: 'css', tags: 'one,two', + standaloneConfig: false, }); const nxJson = readJson(tree, '/nx.json'); expect(nxJson.projects).toEqual({ @@ -42,7 +47,11 @@ describe('app', () => { }); it('should generate files', async () => { - await applicationGenerator(tree, { name: 'myApp', style: 'css' }); + await applicationGenerator(tree, { + name: 'myApp', + style: 'css', + standaloneConfig: false, + }); expect(tree.exists('apps/my-app/tsconfig.json')).toBeTruthy(); expect(tree.exists('apps/my-app/tsconfig.app.json')).toBeTruthy(); expect(tree.exists('apps/my-app/src/pages/index.tsx')).toBeTruthy(); @@ -55,6 +64,7 @@ describe('app', () => { await applicationGenerator(tree, { name: 'myApp', style: 'scss', + standaloneConfig: false, }); expect( tree.exists('apps/my-app/src/pages/index.module.scss') @@ -79,6 +89,7 @@ describe('app', () => { await applicationGenerator(tree, { name: 'myApp', style: 'less', + standaloneConfig: false, }); expect( tree.exists('apps/my-app/src/pages/index.module.less') @@ -103,6 +114,7 @@ describe('app', () => { await applicationGenerator(tree, { name: 'myApp', style: 'styl', + standaloneConfig: false, }); expect( tree.exists('apps/my-app/src/pages/index.module.styl') @@ -127,6 +139,7 @@ describe('app', () => { await applicationGenerator(tree, { name: 'myApp', style: 'styled-components', + standaloneConfig: false, }); expect( tree.exists('apps/my-app/src/pages/index.module.styled-components') @@ -152,6 +165,7 @@ describe('app', () => { await applicationGenerator(tree, { name: 'myApp', style: '@emotion/styled', + standaloneConfig: false, }); expect( tree.exists('apps/my-app/src/pages/index.module.styled-components') @@ -177,6 +191,7 @@ describe('app', () => { await applicationGenerator(tree, { name: 'myApp', style: 'styled-jsx', + standaloneConfig: false, }); const indexContent = tree @@ -206,6 +221,7 @@ describe('app', () => { await applicationGenerator(tree, { name: 'my-app', style: 'css', + standaloneConfig: false, }); expect(tree.read('apps/my-app/jest.config.js', 'utf-8')).toContain( @@ -217,6 +233,7 @@ describe('app', () => { await applicationGenerator(tree, { name: 'my-app', style: 'css', + standaloneConfig: false, }); expect(tree.read('apps/my-app/jest.config.js', 'utf-8')).toContain( @@ -228,6 +245,7 @@ describe('app', () => { await applicationGenerator(tree, { name: 'my-app', style: 'css', + standaloneConfig: false, }); const workspaceJson = readJson(tree, 'workspace.json'); const architectConfig = workspaceJson.projects['my-app'].architect; @@ -241,6 +259,7 @@ describe('app', () => { await applicationGenerator(tree, { name: 'my-app', style: 'css', + standaloneConfig: false, }); const workspaceJson = readJson(tree, 'workspace.json'); const architectConfig = workspaceJson.projects['my-app'].architect; @@ -259,6 +278,7 @@ describe('app', () => { name: 'myApp', style: 'css', unitTestRunner: 'none', + standaloneConfig: false, }); expect(tree.exists('jest.config.js')).toBeFalsy(); expect(tree.exists('apps/my-app/specs/index.spec.tsx')).toBeFalsy(); @@ -271,6 +291,7 @@ describe('app', () => { name: 'myApp', style: 'css', e2eTestRunner: 'none', + standaloneConfig: false, }); expect(tree.exists('apps/my-app-e2e')).toBeFalsy(); const workspaceJson = readJson(tree, 'workspace.json'); @@ -279,7 +300,11 @@ describe('app', () => { }); it('should generate an index component', async () => { - await applicationGenerator(tree, { name: 'myApp', style: 'css' }); + await applicationGenerator(tree, { + name: 'myApp', + style: 'css', + standaloneConfig: false, + }); const appContent = tree.read('apps/my-app/src/pages/index.tsx', 'utf-8'); @@ -290,6 +315,7 @@ describe('app', () => { await applicationGenerator(tree, { name: 'myApp', style: 'css', + standaloneConfig: false, }); const packageJson = readJson(tree, '/package.json'); @@ -349,6 +375,7 @@ describe('app', () => { name: 'myApp', style: 'css', js: true, + standaloneConfig: false, }); expect(tree.exists('apps/my-app/src/pages/index.js')).toBeTruthy(); diff --git a/packages/gatsby/src/generators/application/lib/add-project.ts b/packages/gatsby/src/generators/application/lib/add-project.ts index abfb47dfea..54b9739517 100644 --- a/packages/gatsby/src/generators/application/lib/add-project.ts +++ b/packages/gatsby/src/generators/application/lib/add-project.ts @@ -46,8 +46,13 @@ export function addProject(host: Tree, options: NormalizedSchema) { targets, }; - addProjectConfiguration(host, options.projectName, { - ...project, - ...nxConfig, - }); + addProjectConfiguration( + host, + options.projectName, + { + ...project, + ...nxConfig, + }, + options.standaloneConfig + ); } diff --git a/packages/gatsby/src/generators/application/schema.d.ts b/packages/gatsby/src/generators/application/schema.d.ts index b5bb5a6744..930f3a7484 100644 --- a/packages/gatsby/src/generators/application/schema.d.ts +++ b/packages/gatsby/src/generators/application/schema.d.ts @@ -9,4 +9,5 @@ export interface Schema { e2eTestRunner?: 'cypress' | 'none'; js?: boolean; setParserOptionsProject?: boolean; + standaloneConfig?: boolean; } diff --git a/packages/gatsby/src/generators/application/schema.json b/packages/gatsby/src/generators/application/schema.json index 8bf8de2767..02c52afc24 100644 --- a/packages/gatsby/src/generators/application/schema.json +++ b/packages/gatsby/src/generators/application/schema.json @@ -89,6 +89,11 @@ "type": "boolean", "description": "Whether or not to configure the ESLint \"parserOptions.project\" option. We do not do this by default for lint performance reasons.", "default": false + }, + "standaloneConfig": { + "description": "Split the project configuration into /project.json rather than including it inside workspace.json", + "type": "boolean", + "default": false } }, "required": ["name"] diff --git a/packages/gatsby/src/generators/component/component.spec.ts b/packages/gatsby/src/generators/component/component.spec.ts index 5af206a7ab..3af15d228c 100644 --- a/packages/gatsby/src/generators/component/component.spec.ts +++ b/packages/gatsby/src/generators/component/component.spec.ts @@ -12,7 +12,11 @@ describe('component', () => { tree = createTreeWithEmptyWorkspace(); tree.write('.gitignore', '# empty'); tree.write('.prettierignore', '# empty'); - await applicationGenerator(tree, { name: projectName, style: 'css' }); + await applicationGenerator(tree, { + name: projectName, + style: 'css', + standaloneConfig: false, + }); }); it('should generate component in components directory', async () => { diff --git a/packages/gatsby/src/generators/page/page.spec.ts b/packages/gatsby/src/generators/page/page.spec.ts index cf16f6898d..4b94d18213 100644 --- a/packages/gatsby/src/generators/page/page.spec.ts +++ b/packages/gatsby/src/generators/page/page.spec.ts @@ -12,7 +12,11 @@ describe('component', () => { tree = createTreeWithEmptyWorkspace(); tree.write('.gitignore', '# empty'); tree.write('.prettierignore', '# empty'); - await applicationGenerator(tree, { name: projectName, style: 'css' }); + await applicationGenerator(tree, { + name: projectName, + style: 'css', + standaloneConfig: false, + }); }); it('should generate component in pages directory', async () => { diff --git a/packages/nest/src/schematics/application/schema.d.ts b/packages/nest/src/schematics/application/schema.d.ts index a2fcb16756..6d443f97eb 100644 --- a/packages/nest/src/schematics/application/schema.d.ts +++ b/packages/nest/src/schematics/application/schema.d.ts @@ -10,4 +10,5 @@ export interface Schema { tags?: string; linter: Linter; frontendProject?: string; + standaloneConfig?: boolean; } diff --git a/packages/nest/src/schematics/application/schema.json b/packages/nest/src/schematics/application/schema.json index 817929a1b7..e0047cd6ad 100644 --- a/packages/nest/src/schematics/application/schema.json +++ b/packages/nest/src/schematics/application/schema.json @@ -46,6 +46,11 @@ "frontendProject": { "type": "string", "description": "Frontend project that needs to access this application. This sets up proxy configuration." + }, + "standaloneConfig": { + "description": "Split the project configuration into /project.json rather than including it inside workspace.json", + "type": "boolean", + "default": false } }, "required": [] diff --git a/packages/next/src/generators/application/application.spec.ts b/packages/next/src/generators/application/application.spec.ts index 00b21ee9b8..f0597a9e46 100644 --- a/packages/next/src/generators/application/application.spec.ts +++ b/packages/next/src/generators/application/application.spec.ts @@ -13,7 +13,11 @@ describe('app', () => { describe('not nested', () => { it('should update workspace.json', async () => { - await applicationGenerator(tree, { name: 'myApp', style: 'css' }); + await applicationGenerator(tree, { + name: 'myApp', + style: 'css', + standaloneConfig: false, + }); const workspaceJson = readJson(tree, 'workspace.json'); @@ -29,6 +33,7 @@ describe('app', () => { name: 'myApp', style: 'css', tags: 'one,two', + standaloneConfig: false, }); const nxJson = readJson(tree, 'nx.json'); @@ -45,7 +50,11 @@ describe('app', () => { }); it('should generate files', async () => { - await applicationGenerator(tree, { name: 'myApp', style: 'css' }); + await applicationGenerator(tree, { + name: 'myApp', + style: 'css', + standaloneConfig: false, + }); expect(tree.exists('apps/my-app/tsconfig.json')).toBeTruthy(); expect(tree.exists('apps/my-app/pages/index.tsx')).toBeTruthy(); expect(tree.exists('apps/my-app/specs/index.spec.tsx')).toBeTruthy(); @@ -58,6 +67,7 @@ describe('app', () => { await applicationGenerator(tree, { name: 'myApp', style: 'scss', + standaloneConfig: false, }); expect(tree.exists('apps/my-app/pages/index.module.scss')).toBeTruthy(); @@ -75,6 +85,7 @@ describe('app', () => { await applicationGenerator(tree, { name: 'myApp', style: 'less', + standaloneConfig: false, }); expect(tree.exists('apps/my-app/pages/index.module.less')).toBeTruthy(); @@ -92,6 +103,7 @@ describe('app', () => { await applicationGenerator(tree, { name: 'myApp', style: 'styl', + standaloneConfig: false, }); expect(tree.exists('apps/my-app/pages/index.module.styl')).toBeTruthy(); @@ -109,6 +121,7 @@ describe('app', () => { await applicationGenerator(tree, { name: 'myApp', style: 'styled-components', + standaloneConfig: false, }); expect( @@ -127,6 +140,7 @@ describe('app', () => { await applicationGenerator(tree, { name: 'myApp', style: '@emotion/styled', + standaloneConfig: false, }); expect( @@ -145,6 +159,7 @@ describe('app', () => { await applicationGenerator(tree, { name: 'myApp', style: 'styled-jsx', + standaloneConfig: false, }); const indexContent = tree.read('apps/my-app/pages/index.tsx', 'utf-8'); @@ -163,7 +178,11 @@ describe('app', () => { }); it('should setup jest with tsx support', async () => { - await applicationGenerator(tree, { name: 'my-app', style: 'css' }); + await applicationGenerator(tree, { + name: 'my-app', + style: 'css', + standaloneConfig: false, + }); expect(tree.read('apps/my-app/jest.config.js', 'utf-8')).toContain( `moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],` @@ -171,7 +190,11 @@ describe('app', () => { }); it('should setup jest with SVGR support', async () => { - await applicationGenerator(tree, { name: 'my-app', style: 'css' }); + await applicationGenerator(tree, { + name: 'my-app', + style: 'css', + standaloneConfig: false, + }); expect(tree.read('apps/my-app/jest.config.js', 'utf-8')).toContain( `'^(?!.*\\\\.(js|jsx|ts|tsx|css|json)$)': '@nrwl/react/plugins/jest'` @@ -179,7 +202,11 @@ describe('app', () => { }); it('should set up the nrwl next build builder', async () => { - await applicationGenerator(tree, { name: 'my-app', style: 'css' }); + await applicationGenerator(tree, { + name: 'my-app', + style: 'css', + standaloneConfig: false, + }); const workspaceJson = readJson(tree, 'workspace.json'); const architectConfig = workspaceJson.projects['my-app'].architect; @@ -191,7 +218,11 @@ describe('app', () => { }); it('should set up the nrwl next server builder', async () => { - await applicationGenerator(tree, { name: 'my-app', style: 'css' }); + await applicationGenerator(tree, { + name: 'my-app', + style: 'css', + standaloneConfig: false, + }); const workspaceJson = readJson(tree, 'workspace.json'); const architectConfig = workspaceJson.projects['my-app'].architect; @@ -206,7 +237,11 @@ describe('app', () => { }); it('should set up the nrwl next export builder', async () => { - await applicationGenerator(tree, { name: 'my-app', style: 'css' }); + await applicationGenerator(tree, { + name: 'my-app', + style: 'css', + standaloneConfig: false, + }); const workspaceJson = readJson(tree, 'workspace.json'); const architectConfig = workspaceJson.projects['my-app'].architect; @@ -222,6 +257,7 @@ describe('app', () => { name: 'myApp', style: 'css', unitTestRunner: 'none', + standaloneConfig: false, }); expect(tree.exists('jest.config.js')).toBeFalsy(); expect(tree.exists('apps/my-app/specs/index.spec.tsx')).toBeFalsy(); @@ -234,6 +270,7 @@ describe('app', () => { name: 'myApp', style: 'css', e2eTestRunner: 'none', + standaloneConfig: false, }); expect(tree.exists('apps/my-app-e2e')).toBeFalsy(); const workspaceJson = readJson(tree, 'workspace.json'); @@ -242,7 +279,11 @@ describe('app', () => { }); it('should generate functional components by default', async () => { - await applicationGenerator(tree, { name: 'myApp', style: 'css' }); + await applicationGenerator(tree, { + name: 'myApp', + style: 'css', + standaloneConfig: false, + }); const appContent = tree.read('apps/my-app/pages/index.tsx', 'utf-8'); @@ -255,6 +296,7 @@ describe('app', () => { await applicationGenerator(tree, { name: 'myApp', style: 'css', + standaloneConfig: false, }); const packageJson = readJson(tree, '/package.json'); @@ -311,6 +353,7 @@ describe('app', () => { name: 'myApp', style: 'css', linter: Linter.TsLint, + standaloneConfig: false, }); const tslintJson = readJson(tree, 'apps/my-app/tslint.json'); @@ -335,6 +378,7 @@ describe('app', () => { name: 'myApp', style: 'css', js: true, + standaloneConfig: false, }); expect(tree.exists('apps/my-app/pages/index.js')).toBeTruthy(); diff --git a/packages/next/src/generators/application/lib/add-project.ts b/packages/next/src/generators/application/lib/add-project.ts index 1e39d76ec1..52440329a8 100644 --- a/packages/next/src/generators/application/lib/add-project.ts +++ b/packages/next/src/generators/application/lib/add-project.ts @@ -63,8 +63,13 @@ export function addProject(host: Tree, options: NormalizedSchema) { targets, }; - addProjectConfiguration(host, options.projectName, { - ...project, - ...nxConfig, - }); + addProjectConfiguration( + host, + options.projectName, + { + ...project, + ...nxConfig, + }, + options.standaloneConfig + ); } diff --git a/packages/next/src/generators/application/schema.d.ts b/packages/next/src/generators/application/schema.d.ts index 9aabdb93ff..4a999a212a 100644 --- a/packages/next/src/generators/application/schema.d.ts +++ b/packages/next/src/generators/application/schema.d.ts @@ -14,4 +14,5 @@ export interface Schema { skipWorkspaceJson?: boolean; js?: boolean; setParserOptionsProject?: boolean; + standaloneConfig?: boolean; } diff --git a/packages/next/src/generators/application/schema.json b/packages/next/src/generators/application/schema.json index ce586d5c16..f3424c9b43 100644 --- a/packages/next/src/generators/application/schema.json +++ b/packages/next/src/generators/application/schema.json @@ -109,6 +109,11 @@ "type": "boolean", "description": "Whether or not to configure the ESLint \"parserOptions.project\" option. We do not do this by default for lint performance reasons.", "default": false + }, + "standaloneConfig": { + "description": "Split the project configuration into /project.json rather than including it inside workspace.json", + "type": "boolean", + "default": false } }, "required": [] diff --git a/packages/next/src/generators/component/component.spec.ts b/packages/next/src/generators/component/component.spec.ts index 674d8bc3f8..e6189cef10 100644 --- a/packages/next/src/generators/component/component.spec.ts +++ b/packages/next/src/generators/component/component.spec.ts @@ -10,7 +10,11 @@ describe('component', () => { beforeEach(async () => { projectName = 'my-app'; tree = createTreeWithEmptyWorkspace(); - await applicationGenerator(tree, { name: projectName, style: 'css' }); + await applicationGenerator(tree, { + name: projectName, + style: 'css', + standaloneConfig: false, + }); }); it('should generate component in components directory', async () => { diff --git a/packages/next/src/generators/page/page.spec.ts b/packages/next/src/generators/page/page.spec.ts index 3634864c6d..950b67eb71 100644 --- a/packages/next/src/generators/page/page.spec.ts +++ b/packages/next/src/generators/page/page.spec.ts @@ -10,7 +10,11 @@ describe('component', () => { beforeEach(async () => { projectName = 'my-app'; tree = createTreeWithEmptyWorkspace(); - await applicationGenerator(tree, { name: projectName, style: 'css' }); + await applicationGenerator(tree, { + name: projectName, + style: 'css', + standaloneConfig: false, + }); }); it('should generate component in pages directory', async () => { diff --git a/packages/node/src/generators/application/application.spec.ts b/packages/node/src/generators/application/application.spec.ts index 0a373e603d..58063be6cd 100644 --- a/packages/node/src/generators/application/application.spec.ts +++ b/packages/node/src/generators/application/application.spec.ts @@ -31,7 +31,10 @@ describe('app', () => { describe('not nested', () => { it('should update workspace.json', async () => { - await applicationGenerator(tree, { name: 'myNodeApp' }); + await applicationGenerator(tree, { + name: 'myNodeApp', + standaloneConfig: false, + }); const workspaceJson = readJson(tree, '/workspace.json'); const project = workspaceJson.projects['my-node-app']; expect(project.root).toEqual('apps/my-node-app'); @@ -79,7 +82,11 @@ describe('app', () => { }); it('should update nx.json', async () => { - await applicationGenerator(tree, { name: 'myNodeApp', tags: 'one,two' }); + await applicationGenerator(tree, { + name: 'myNodeApp', + tags: 'one,two', + standaloneConfig: false, + }); const nxJson = readJson(tree, '/nx.json'); expect(nxJson.projects).toEqual({ 'my-node-app': { @@ -89,7 +96,10 @@ describe('app', () => { }); it('should generate files', async () => { - await applicationGenerator(tree, { name: 'myNodeApp' }); + await applicationGenerator(tree, { + name: 'myNodeApp', + standaloneConfig: false, + }); expect(tree.exists(`apps/my-node-app/jest.config.js`)).toBeTruthy(); expect(tree.exists('apps/my-node-app/src/main.ts')).toBeTruthy(); @@ -158,6 +168,7 @@ describe('app', () => { await applicationGenerator(tree, { name: 'myNodeApp', directory: 'myDir', + standaloneConfig: false, }); const workspaceJson = readJson(tree, '/workspace.json'); @@ -183,6 +194,7 @@ describe('app', () => { name: 'myNodeApp', directory: 'myDir', tags: 'one,two', + standaloneConfig: false, }); const nxJson = readJson(tree, '/nx.json'); expect(nxJson.projects).toEqual({ @@ -201,6 +213,7 @@ describe('app', () => { await applicationGenerator(tree, { name: 'myNodeApp', directory: 'myDir', + standaloneConfig: false, }); // Make sure these exist @@ -237,6 +250,7 @@ describe('app', () => { await applicationGenerator(tree, { name: 'myNodeApp', unitTestRunner: 'none', + standaloneConfig: false, }); expect(tree.exists('jest.config.js')).toBeFalsy(); expect(tree.exists('apps/my-node-app/src/test-setup.ts')).toBeFalsy(); @@ -268,6 +282,7 @@ describe('app', () => { await applicationGenerator(tree, { name: 'myNodeApp', frontendProject: 'my-frontend', + standaloneConfig: false, }); expect(tree.exists('apps/my-frontend/proxy.conf.json')).toBeTruthy(); @@ -284,11 +299,13 @@ describe('app', () => { await applicationGenerator(tree, { name: 'cart', frontendProject: 'my-frontend', + standaloneConfig: false, }); await applicationGenerator(tree, { name: 'billing', frontendProject: 'my-frontend', + standaloneConfig: false, }); expect(tree.exists('apps/my-frontend/proxy.conf.json')).toBeTruthy(); @@ -305,6 +322,7 @@ describe('app', () => { await applicationGenerator(tree, { name: 'myNodeApp', frontendProject: 'myFrontend', + standaloneConfig: false, }); expect(tree.exists('apps/my-frontend/proxy.conf.json')).toBeTruthy(); diff --git a/packages/node/src/generators/application/application.ts b/packages/node/src/generators/application/application.ts index 0f386e6b0e..d6c6e1780d 100644 --- a/packages/node/src/generators/application/application.ts +++ b/packages/node/src/generators/application/application.ts @@ -93,7 +93,12 @@ function addProject(tree: Tree, options: NormalizedSchema) { project.targets.build = getBuildConfig(project, options); project.targets.serve = getServeConfig(options); - addProjectConfiguration(tree, options.name, project); + addProjectConfiguration( + tree, + options.name, + project, + options.standaloneConfig + ); const workspace = readWorkspaceConfiguration(tree); diff --git a/packages/node/src/generators/application/schema.d.ts b/packages/node/src/generators/application/schema.d.ts index cec497cfac..4564398f11 100644 --- a/packages/node/src/generators/application/schema.d.ts +++ b/packages/node/src/generators/application/schema.d.ts @@ -13,4 +13,5 @@ export interface Schema { js?: boolean; pascalCaseFiles?: boolean; setParserOptionsProject?: boolean; + standaloneConfig?: boolean; } diff --git a/packages/node/src/generators/application/schema.json b/packages/node/src/generators/application/schema.json index 5879ebdba0..3bc13eaadf 100644 --- a/packages/node/src/generators/application/schema.json +++ b/packages/node/src/generators/application/schema.json @@ -68,6 +68,11 @@ "type": "boolean", "description": "Whether or not to configure the ESLint \"parserOptions.project\" option. We do not do this by default for lint performance reasons.", "default": false + }, + "standaloneConfig": { + "description": "Split the project configuration into /project.json rather than including it inside workspace.json", + "type": "boolean", + "default": false } }, "required": [] diff --git a/packages/node/src/generators/library/library.spec.ts b/packages/node/src/generators/library/library.spec.ts index d252a90749..385ad62a04 100644 --- a/packages/node/src/generators/library/library.spec.ts +++ b/packages/node/src/generators/library/library.spec.ts @@ -13,7 +13,7 @@ describe('lib', () => { describe('not nested', () => { it('should update workspace.json', async () => { - await libraryGenerator(tree, { name: 'myLib' }); + await libraryGenerator(tree, { name: 'myLib', standaloneConfig: false }); const workspaceJson = readJson(tree, '/workspace.json'); expect(workspaceJson.projects['my-lib'].root).toEqual('libs/my-lib'); expect(workspaceJson.projects['my-lib'].architect.build).toBeUndefined(); @@ -38,6 +38,7 @@ describe('lib', () => { name: 'myLib', rootDir: './src', buildable: true, + standaloneConfig: false, }); const workspaceJson = readJson(tree, '/workspace.json'); expect( @@ -47,7 +48,11 @@ describe('lib', () => { }); it('should update nx.json', async () => { - await libraryGenerator(tree, { name: 'myLib', tags: 'one,two' }); + await libraryGenerator(tree, { + name: 'myLib', + tags: 'one,two', + standaloneConfig: false, + }); const nxJson = readJson(tree, '/nx.json'); expect(nxJson.projects).toEqual({ 'my-lib': { @@ -57,7 +62,7 @@ describe('lib', () => { }); it('should update root tsconfig.base.json', async () => { - await libraryGenerator(tree, { name: 'myLib' }); + await libraryGenerator(tree, { name: 'myLib', standaloneConfig: false }); const tsconfigJson = readJson(tree, '/tsconfig.base.json'); expect(tsconfigJson.compilerOptions.paths['@proj/my-lib']).toEqual([ 'libs/my-lib/src/index.ts', @@ -65,7 +70,7 @@ describe('lib', () => { }); it('should create a local tsconfig.json', async () => { - await libraryGenerator(tree, { name: 'myLib' }); + await libraryGenerator(tree, { name: 'myLib', standaloneConfig: false }); const tsconfigJson = readJson(tree, 'libs/my-lib/tsconfig.json'); expect(tsconfigJson).toMatchInlineSnapshot(` Object { @@ -85,20 +90,20 @@ describe('lib', () => { }); it('should extend the local tsconfig.json with tsconfig.spec.json', async () => { - await libraryGenerator(tree, { name: 'myLib' }); + await libraryGenerator(tree, { name: 'myLib', standaloneConfig: false }); const tsconfigJson = readJson(tree, 'libs/my-lib/tsconfig.spec.json'); expect(tsconfigJson.extends).toEqual('./tsconfig.json'); }); it('should extend the local tsconfig.json with tsconfig.lib.json', async () => { - await libraryGenerator(tree, { name: 'myLib' }); + await libraryGenerator(tree, { name: 'myLib', standaloneConfig: false }); const tsconfigJson = readJson(tree, 'libs/my-lib/tsconfig.lib.json'); expect(tsconfigJson.compilerOptions.types).toContain('node'); expect(tsconfigJson.extends).toEqual('./tsconfig.json'); }); it('should generate files', async () => { - await libraryGenerator(tree, { name: 'myLib' }); + await libraryGenerator(tree, { name: 'myLib', standaloneConfig: false }); expect(tree.exists(`libs/my-lib/jest.config.js`)).toBeTruthy(); expect(tree.exists('libs/my-lib/src/index.ts')).toBeTruthy(); @@ -147,6 +152,7 @@ describe('lib', () => { name: 'myLib', directory: 'myDir', tags: 'one', + standaloneConfig: false, }); const nxJson = readJson(tree, '/nx.json'); expect(nxJson.projects).toEqual({ @@ -159,6 +165,7 @@ describe('lib', () => { name: 'myLib2', directory: 'myDir', tags: 'one,two', + standaloneConfig: false, }); const nxJson2 = readJson(tree, '/nx.json'); expect(nxJson2.projects).toEqual({ @@ -172,13 +179,21 @@ describe('lib', () => { }); it('should generate files', async () => { - await libraryGenerator(tree, { name: 'myLib', directory: 'myDir' }); + await libraryGenerator(tree, { + name: 'myLib', + directory: 'myDir', + standaloneConfig: false, + }); expect(tree.exists(`libs/my-dir/my-lib/jest.config.js`)).toBeTruthy(); expect(tree.exists('libs/my-dir/my-lib/src/index.ts')).toBeTruthy(); }); it('should update workspace.json', async () => { - await libraryGenerator(tree, { name: 'myLib', directory: 'myDir' }); + await libraryGenerator(tree, { + name: 'myLib', + directory: 'myDir', + standaloneConfig: false, + }); const workspaceJson = readJson(tree, '/workspace.json'); expect(workspaceJson.projects['my-dir-my-lib'].root).toEqual( @@ -193,7 +208,11 @@ describe('lib', () => { }); it('should update tsconfig.json', async () => { - await libraryGenerator(tree, { name: 'myLib', directory: 'myDir' }); + await libraryGenerator(tree, { + name: 'myLib', + directory: 'myDir', + standaloneConfig: false, + }); const tsconfigJson = readJson(tree, '/tsconfig.base.json'); expect(tsconfigJson.compilerOptions.paths['@proj/my-dir/my-lib']).toEqual( ['libs/my-dir/my-lib/src/index.ts'] @@ -211,6 +230,7 @@ describe('lib', () => { name: 'myLib', directory: 'myDir', publishable: true, + standaloneConfig: false, }); } catch (e) { expect(e.message).toContain( @@ -220,7 +240,11 @@ describe('lib', () => { }); it('should create a local tsconfig.json', async () => { - await libraryGenerator(tree, { name: 'myLib', directory: 'myDir' }); + await libraryGenerator(tree, { + name: 'myLib', + directory: 'myDir', + standaloneConfig: false, + }); const tsconfigJson = readJson(tree, 'libs/my-dir/my-lib/tsconfig.json'); expect(tsconfigJson.extends).toEqual('../../../tsconfig.base.json'); @@ -239,6 +263,7 @@ describe('lib', () => { name: 'myLib', directory: 'myDir', simpleModuleName: true, + standaloneConfig: false, }); expect(tree.exists(`libs/my-dir/my-lib/jest.config.js`)).toBeTruthy(); expect(tree.exists('libs/my-dir/my-lib/src/index.ts')).toBeTruthy(); @@ -253,7 +278,11 @@ describe('lib', () => { describe('--unit-test-runner none', () => { it('should not generate test configuration', async () => { - await libraryGenerator(tree, { name: 'myLib', unitTestRunner: 'none' }); + await libraryGenerator(tree, { + name: 'myLib', + unitTestRunner: 'none', + standaloneConfig: false, + }); expect(tree.exists('libs/my-lib/tsconfig.spec.json')).toBeFalsy(); expect(tree.exists('libs/my-lib/jest.config.js')).toBeFalsy(); expect(tree.exists('libs/my-lib/lib/my-lib.spec.ts')).toBeFalsy(); @@ -282,7 +311,11 @@ describe('lib', () => { describe('buildable package', () => { it('should have a builder defined', async () => { - await libraryGenerator(tree, { name: 'myLib', buildable: true }); + await libraryGenerator(tree, { + name: 'myLib', + buildable: true, + standaloneConfig: false, + }); const workspaceJson = readJson(tree, '/workspace.json'); expect(workspaceJson.projects['my-lib'].root).toEqual('libs/my-lib'); @@ -314,6 +347,7 @@ describe('lib', () => { name: 'myLib', publishable: true, importPath: '@proj/mylib', + standaloneConfig: false, }); const workspaceJson = readJson(tree, '/workspace.json'); @@ -327,6 +361,7 @@ describe('lib', () => { name: 'mylib', publishable: true, importPath: '@proj/mylib', + standaloneConfig: false, }); let packageJsonContent = readJson(tree, 'libs/mylib/package.json'); @@ -342,6 +377,7 @@ describe('lib', () => { publishable: true, directory: 'myDir', importPath: '@myorg/lib', + standaloneConfig: false, }); const packageJson = readJson(tree, 'libs/my-dir/my-lib/package.json'); const tsconfigJson = readJson(tree, '/tsconfig.base.json'); @@ -357,6 +393,7 @@ describe('lib', () => { name: 'myLib1', publishable: true, importPath: '@myorg/lib', + standaloneConfig: false, }); try { @@ -364,6 +401,7 @@ describe('lib', () => { name: 'myLib2', publishable: true, importPath: '@myorg/lib', + standaloneConfig: false, }); } catch (e) { expect(e.message).toContain( diff --git a/packages/node/src/generators/library/schema.d.ts b/packages/node/src/generators/library/schema.d.ts index 2ff4c8b870..91e73eced1 100644 --- a/packages/node/src/generators/library/schema.d.ts +++ b/packages/node/src/generators/library/schema.d.ts @@ -18,4 +18,5 @@ export interface Schema { js?: boolean; pascalCaseFiles?: boolean; strict?: boolean; + standaloneConfig?: boolean; } diff --git a/packages/node/src/generators/library/schema.json b/packages/node/src/generators/library/schema.json index a103a89acd..b21d5f1cfc 100644 --- a/packages/node/src/generators/library/schema.json +++ b/packages/node/src/generators/library/schema.json @@ -101,6 +101,11 @@ "type": "boolean", "description": "Whether to enable tsconfig strict mode or not.", "default": false + }, + "standaloneConfig": { + "description": "Split the project configuration into /project.json rather than including it inside workspace.json", + "type": "boolean", + "default": false } }, "required": ["name"] diff --git a/packages/nx-plugin/src/generators/e2e-project/e2e.spec.ts b/packages/nx-plugin/src/generators/e2e-project/e2e.spec.ts index 6d75e13173..be61eaad4a 100644 --- a/packages/nx-plugin/src/generators/e2e-project/e2e.spec.ts +++ b/packages/nx-plugin/src/generators/e2e-project/e2e.spec.ts @@ -25,6 +25,7 @@ describe('NxPlugin e2e-project Generator', () => { pluginName: 'my-plugin', pluginOutputPath: `dist/libs/my-plugin`, npmPackageName: '@proj/my-plugin', + standaloneConfig: false, }) ).resolves.not.toThrow(); @@ -33,6 +34,7 @@ describe('NxPlugin e2e-project Generator', () => { pluginName: 'my-nonexistentplugin', pluginOutputPath: `dist/libs/my-nonexistentplugin`, npmPackageName: '@proj/my-nonexistentplugin', + standaloneConfig: false, }) ).rejects.toThrow(); }); @@ -42,6 +44,7 @@ describe('NxPlugin e2e-project Generator', () => { pluginName: 'my-plugin', pluginOutputPath: `dist/libs/my-plugin`, npmPackageName: '@proj/my-plugin', + standaloneConfig: false, }); expect(tree.exists('apps/my-plugin-e2e/tsconfig.json')).toBeTruthy(); @@ -56,6 +59,7 @@ describe('NxPlugin e2e-project Generator', () => { pluginOutputPath: `dist/libs/namespace/my-plugin`, npmPackageName: '@proj/namespace-my-plugin', projectDirectory: 'namespace/my-plugin', + standaloneConfig: false, }); const project = readProjectConfiguration(tree, 'my-plugin-e2e'); @@ -67,6 +71,7 @@ describe('NxPlugin e2e-project Generator', () => { pluginName: 'my-plugin', pluginOutputPath: `dist/libs/my-plugin`, npmPackageName: '@proj/my-plugin', + standaloneConfig: false, }); expect(readJson(tree, 'nx.json')).toMatchObject({ @@ -84,6 +89,7 @@ describe('NxPlugin e2e-project Generator', () => { pluginName: 'my-plugin', pluginOutputPath: `dist/libs/my-plugin`, npmPackageName: '@proj/my-plugin', + standaloneConfig: false, }); const project = readProjectConfiguration(tree, 'my-plugin-e2e'); @@ -106,6 +112,7 @@ describe('NxPlugin e2e-project Generator', () => { pluginName: 'my-plugin', pluginOutputPath: `dist/libs/my-plugin`, npmPackageName: '@proj/my-plugin', + standaloneConfig: false, }); const project = readProjectConfiguration(tree, 'my-plugin-e2e'); diff --git a/packages/nx-plugin/src/generators/e2e-project/e2e.ts b/packages/nx-plugin/src/generators/e2e-project/e2e.ts index e7794fd2ef..10aefcf54d 100644 --- a/packages/nx-plugin/src/generators/e2e-project/e2e.ts +++ b/packages/nx-plugin/src/generators/e2e-project/e2e.ts @@ -56,23 +56,28 @@ function addFiles(host: Tree, options: NormalizedSchema) { } function updateWorkspaceConfiguration(host: Tree, options: NormalizedSchema) { - addProjectConfiguration(host, options.projectName, { - root: options.projectRoot, - projectType: 'application', - sourceRoot: `${options.projectRoot}/src`, - targets: { - e2e: { - executor: '@nrwl/nx-plugin:e2e', - options: { - target: `${options.pluginName}:build`, - npmPackageName: options.npmPackageName, - pluginOutputPath: options.pluginOutputPath, + addProjectConfiguration( + host, + options.projectName, + { + root: options.projectRoot, + projectType: 'application', + sourceRoot: `${options.projectRoot}/src`, + targets: { + e2e: { + executor: '@nrwl/nx-plugin:e2e', + options: { + target: `${options.pluginName}:build`, + npmPackageName: options.npmPackageName, + pluginOutputPath: options.pluginOutputPath, + }, }, }, + tags: [], + implicitDependencies: [options.pluginName], }, - tags: [], - implicitDependencies: [options.pluginName], - }); + options.standaloneConfig + ); } async function addJest(host: Tree, options: NormalizedSchema) { diff --git a/packages/nx-plugin/src/generators/e2e-project/schema.d.ts b/packages/nx-plugin/src/generators/e2e-project/schema.d.ts index 3d8c468f36..ae365e0e7f 100644 --- a/packages/nx-plugin/src/generators/e2e-project/schema.d.ts +++ b/packages/nx-plugin/src/generators/e2e-project/schema.d.ts @@ -4,4 +4,5 @@ export interface Schema { projectDirectory?: string; pluginOutputPath?: string; jestConfig?: string; + standaloneConfig?: boolean; } diff --git a/packages/nx-plugin/src/generators/e2e-project/schema.json b/packages/nx-plugin/src/generators/e2e-project/schema.json index e71763a556..5731b2cb64 100644 --- a/packages/nx-plugin/src/generators/e2e-project/schema.json +++ b/packages/nx-plugin/src/generators/e2e-project/schema.json @@ -29,6 +29,11 @@ "type": "string", "description": "Spec tsconfig file", "x-deprecated": true + }, + "standaloneConfig": { + "description": "Split the project configuration into /project.json rather than including it inside workspace.json", + "type": "boolean", + "default": false } }, "required": ["pluginName", "npmPackageName"], diff --git a/packages/nx-plugin/src/generators/plugin/plugin.ts b/packages/nx-plugin/src/generators/plugin/plugin.ts index 7960152854..803eaa07c7 100644 --- a/packages/nx-plugin/src/generators/plugin/plugin.ts +++ b/packages/nx-plugin/src/generators/plugin/plugin.ts @@ -150,6 +150,7 @@ export async function pluginGenerator(host: Tree, schema: Schema) { projectDirectory: options.projectDirectory, pluginOutputPath: `dist/${options.libsDir}/${options.projectDirectory}`, npmPackageName: options.npmPackageName, + standaloneConfig: options.standaloneConfig, }); await formatFiles(host); diff --git a/packages/nx-plugin/src/generators/plugin/schema.d.ts b/packages/nx-plugin/src/generators/plugin/schema.d.ts index be6fdf8468..0dbe9a44e1 100644 --- a/packages/nx-plugin/src/generators/plugin/schema.d.ts +++ b/packages/nx-plugin/src/generators/plugin/schema.d.ts @@ -9,4 +9,5 @@ export interface Schema { tags?: string; unitTestRunner: 'jest' | 'none'; linter: Linter; + standaloneConfig?: boolean; } diff --git a/packages/nx-plugin/src/generators/plugin/schema.json b/packages/nx-plugin/src/generators/plugin/schema.json index 3d1c1aca99..40f8b616c8 100644 --- a/packages/nx-plugin/src/generators/plugin/schema.json +++ b/packages/nx-plugin/src/generators/plugin/schema.json @@ -55,6 +55,11 @@ "type": "boolean", "default": false, "description": "Do not update tsconfig.json for development experience." + }, + "standaloneConfig": { + "description": "Split the project configuration into /project.json rather than including it inside workspace.json", + "type": "boolean", + "default": false } }, "required": ["name"], diff --git a/packages/react/src/generators/application/application.spec.ts b/packages/react/src/generators/application/application.spec.ts index 3f36730fd4..b73b3e77e3 100644 --- a/packages/react/src/generators/application/application.spec.ts +++ b/packages/react/src/generators/application/application.spec.ts @@ -20,6 +20,7 @@ describe('app', () => { linter: Linter.EsLint, style: 'css', strict: false, + standaloneConfig: false, }; beforeEach(() => { diff --git a/packages/react/src/generators/application/lib/add-project.ts b/packages/react/src/generators/application/lib/add-project.ts index 66fe1587f7..1cf92861a9 100644 --- a/packages/react/src/generators/application/lib/add-project.ts +++ b/packages/react/src/generators/application/lib/add-project.ts @@ -22,10 +22,15 @@ export function addProject(host, options: NormalizedSchema) { }, }; - addProjectConfiguration(host, options.projectName, { - ...project, - ...nxConfig, - }); + addProjectConfiguration( + host, + options.projectName, + { + ...project, + ...nxConfig, + }, + options.standaloneConfig + ); } function maybeJs(options: NormalizedSchema, path: string): string { diff --git a/packages/react/src/generators/application/schema.d.ts b/packages/react/src/generators/application/schema.d.ts index 43f3c7f771..fd18998712 100644 --- a/packages/react/src/generators/application/schema.d.ts +++ b/packages/react/src/generators/application/schema.d.ts @@ -19,6 +19,7 @@ export interface Schema { globalCss?: boolean; strict?: boolean; setParserOptionsProject?: boolean; + standaloneConfig?: boolean; } export interface NormalizedSchema extends Schema { diff --git a/packages/react/src/generators/application/schema.json b/packages/react/src/generators/application/schema.json index 0378af1b86..6a9924ca3f 100644 --- a/packages/react/src/generators/application/schema.json +++ b/packages/react/src/generators/application/schema.json @@ -145,6 +145,11 @@ "type": "boolean", "description": "Whether or not to configure the ESLint \"parserOptions.project\" option. We do not do this by default for lint performance reasons.", "default": false + }, + "standaloneConfig": { + "description": "Split the project configuration into /project.json rather than including it inside workspace.json", + "type": "boolean", + "default": false } }, "required": [] diff --git a/packages/react/src/generators/component-cypress-spec/component-cypress-spec.spec.ts b/packages/react/src/generators/component-cypress-spec/component-cypress-spec.spec.ts index e105c86413..2a9db0d95c 100644 --- a/packages/react/src/generators/component-cypress-spec/component-cypress-spec.spec.ts +++ b/packages/react/src/generators/component-cypress-spec/component-cypress-spec.spec.ts @@ -154,6 +154,7 @@ describe('react:component-cypress-spec', () => { skipFormat: true, style: 'css', unitTestRunner: 'none', + standaloneConfig: false, }); await componentCypressSpecGenerator(appTree, { componentPath: `lib/test-ui-lib.tsx`, @@ -187,6 +188,7 @@ export async function createTestUILib( skipTsConfig: false, style: 'css', unitTestRunner: 'jest', + standaloneConfig: false, }); // create some Nx app that we'll use to generate the cypress @@ -200,6 +202,7 @@ export async function createTestUILib( skipFormat: true, style: 'css', unitTestRunner: 'none', + standaloneConfig: false, }); return appTree; diff --git a/packages/react/src/generators/component-story/component-story.spec.ts b/packages/react/src/generators/component-story/component-story.spec.ts index 85ba396c11..a75c662c4d 100644 --- a/packages/react/src/generators/component-story/component-story.spec.ts +++ b/packages/react/src/generators/component-story/component-story.spec.ts @@ -426,6 +426,7 @@ export async function createTestUILib( skipTsConfig: false, style: 'css', unitTestRunner: 'jest', + standaloneConfig: false, }); if (useEsLint) { diff --git a/packages/react/src/generators/component/component.spec.ts b/packages/react/src/generators/component/component.spec.ts index 31d774f987..8a477738e5 100644 --- a/packages/react/src/generators/component/component.spec.ts +++ b/packages/react/src/generators/component/component.spec.ts @@ -10,8 +10,8 @@ describe('component', () => { beforeEach(async () => { projectName = 'my-lib'; appTree = createTreeWithEmptyWorkspace(); - await createApp(appTree, 'my-app'); - await createLib(appTree, projectName); + await createApp(appTree, 'my-app', false); + await createLib(appTree, projectName, false); jest.spyOn(logger, 'warn').mockImplementation(() => {}); jest.spyOn(logger, 'debug').mockImplementation(() => {}); }); diff --git a/packages/react/src/generators/library/library.spec.ts b/packages/react/src/generators/library/library.spec.ts index 79b79c3a03..b777d2e9aa 100644 --- a/packages/react/src/generators/library/library.spec.ts +++ b/packages/react/src/generators/library/library.spec.ts @@ -17,6 +17,7 @@ describe('lib', () => { style: 'css', component: true, strict: true, + standaloneConfig: false, }; beforeEach(() => { @@ -349,6 +350,7 @@ describe('lib', () => { name: 'myApp', routing: true, style: 'css', + standaloneConfig: false, }); await libraryGenerator(appTree, { @@ -375,6 +377,7 @@ describe('lib', () => { unitTestRunner: 'jest', name: 'myApp', style: 'css', + standaloneConfig: false, }); await libraryGenerator(appTree, { diff --git a/packages/react/src/generators/library/library.ts b/packages/react/src/generators/library/library.ts index 71d3b4481a..d175f1957b 100644 --- a/packages/react/src/generators/library/library.ts +++ b/packages/react/src/generators/library/library.ts @@ -202,13 +202,18 @@ function addProject(host: Tree, options: NormalizedSchema) { }; } - addProjectConfiguration(host, options.name, { - root: options.projectRoot, - sourceRoot: joinPathFragments(options.projectRoot, 'src'), - projectType: 'library', - tags: options.parsedTags, - targets, - }); + addProjectConfiguration( + host, + options.name, + { + root: options.projectRoot, + sourceRoot: joinPathFragments(options.projectRoot, 'src'), + projectType: 'library', + tags: options.parsedTags, + targets, + }, + options.standaloneConfig + ); } function updateTsConfig(tree: Tree, options: NormalizedSchema) { diff --git a/packages/react/src/generators/library/schema.d.ts b/packages/react/src/generators/library/schema.d.ts index 35a93ef068..2757f5f4ba 100644 --- a/packages/react/src/generators/library/schema.d.ts +++ b/packages/react/src/generators/library/schema.d.ts @@ -21,4 +21,5 @@ export interface Schema { globalCss?: boolean; strict?: boolean; setParserOptionsProject?: boolean; + standaloneConfig?: boolean; } diff --git a/packages/react/src/generators/library/schema.json b/packages/react/src/generators/library/schema.json index ce1ac33fc5..87061a943b 100644 --- a/packages/react/src/generators/library/schema.json +++ b/packages/react/src/generators/library/schema.json @@ -150,6 +150,11 @@ "type": "boolean", "description": "Whether or not to configure the ESLint \"parserOptions.project\" option. We do not do this by default for lint performance reasons.", "default": false + }, + "standaloneConfig": { + "description": "Split the project configuration into /project.json rather than including it inside workspace.json", + "type": "boolean", + "default": false } }, "required": ["name"] diff --git a/packages/react/src/generators/redux/redux.spec.ts b/packages/react/src/generators/redux/redux.spec.ts index 0bc8cae420..ad56075044 100644 --- a/packages/react/src/generators/redux/redux.spec.ts +++ b/packages/react/src/generators/redux/redux.spec.ts @@ -16,6 +16,7 @@ describe('redux', () => { skipTsConfig: false, style: 'css', unitTestRunner: 'jest', + standaloneConfig: false, }); }); @@ -54,6 +55,7 @@ describe('redux', () => { style: 'css', unitTestRunner: 'none', name: 'my-app', + standaloneConfig: false, }); await reduxGenerator(appTree, { name: 'my-slice', diff --git a/packages/react/src/generators/stories/stories-app.spec.ts b/packages/react/src/generators/stories/stories-app.spec.ts index 053305b033..8fe5bf3d52 100644 --- a/packages/react/src/generators/stories/stories-app.spec.ts +++ b/packages/react/src/generators/stories/stories-app.spec.ts @@ -102,6 +102,7 @@ export async function createTestUIApp( unitTestRunner: 'none', name: libName, js: plainJS, + standaloneConfig: false, }); return appTree; } diff --git a/packages/react/src/generators/stories/stories-lib.spec.ts b/packages/react/src/generators/stories/stories-lib.spec.ts index b6b0013894..400244d70e 100644 --- a/packages/react/src/generators/stories/stories-lib.spec.ts +++ b/packages/react/src/generators/stories/stories-lib.spec.ts @@ -106,6 +106,7 @@ export async function createTestUILib( style: 'css', unitTestRunner: 'none', name: libName, + standaloneConfig: false, }); // create some Nx app that we'll use to generate the cypress @@ -120,6 +121,7 @@ export async function createTestUILib( unitTestRunner: 'none', name: `${libName}-e2e`, js: plainJS, + standaloneConfig: false, }); return appTree; } diff --git a/packages/react/src/generators/storybook-configuration/configuration.spec.ts b/packages/react/src/generators/storybook-configuration/configuration.spec.ts index e3dfe3645a..aab0c54a9a 100644 --- a/packages/react/src/generators/storybook-configuration/configuration.spec.ts +++ b/packages/react/src/generators/storybook-configuration/configuration.spec.ts @@ -32,6 +32,7 @@ describe('react:storybook-configuration', () => { await storybookConfigurationGenerator(appTree, { name: 'test-ui-lib', configureCypress: true, + standaloneConfig: false, }); expect(appTree.exists('libs/test-ui-lib/.storybook/main.js')).toBeTruthy(); @@ -47,6 +48,7 @@ describe('react:storybook-configuration', () => { name: 'test-ui-lib', generateStories: true, configureCypress: false, + standaloneConfig: false, }); expect( @@ -80,6 +82,7 @@ describe('react:storybook-configuration', () => { generateStories: true, configureCypress: false, js: true, + standaloneConfig: false, }); expect( @@ -92,6 +95,7 @@ describe('react:storybook-configuration', () => { await storybookConfigurationGenerator(appTree, { name: 'test-ui-app', configureCypress: true, + standaloneConfig: false, }); expect(appTree.exists('apps/test-ui-app/.storybook/main.js')).toBeTruthy(); @@ -115,6 +119,7 @@ describe('react:storybook-configuration', () => { name: 'test-ui-app', generateStories: true, configureCypress: false, + standaloneConfig: false, }); // Currently the auto-generate stories feature only picks up components under the 'lib' directory. @@ -140,6 +145,7 @@ describe('react:storybook-configuration', () => { configureCypress: true, generateCypressSpecs: true, cypressDirectory: 'one/two', + standaloneConfig: false, }); [ 'apps/one/two/test-ui-lib-e2e/cypress.json', @@ -172,6 +178,7 @@ export async function createTestUILib( style: 'css', unitTestRunner: 'none', name: libName, + standaloneConfig: false, }); return appTree; } @@ -191,6 +198,7 @@ export async function createTestAppLib( unitTestRunner: 'none', name: libName, js: plainJS, + standaloneConfig: false, }); await componentGenerator(appTree, { diff --git a/packages/react/src/generators/storybook-configuration/configuration.ts b/packages/react/src/generators/storybook-configuration/configuration.ts index fba40fbccc..05cc44cdf8 100644 --- a/packages/react/src/generators/storybook-configuration/configuration.ts +++ b/packages/react/src/generators/storybook-configuration/configuration.ts @@ -36,6 +36,7 @@ export async function storybookConfigurationGenerator( js: schema.js, linter: schema.linter, cypressDirectory: schema.cypressDirectory, + standaloneConfig: schema.standaloneConfig, }); if (schema.generateStories) { diff --git a/packages/react/src/generators/storybook-configuration/schema.d.ts b/packages/react/src/generators/storybook-configuration/schema.d.ts index b13efc8190..3de38336e0 100644 --- a/packages/react/src/generators/storybook-configuration/schema.d.ts +++ b/packages/react/src/generators/storybook-configuration/schema.d.ts @@ -8,4 +8,5 @@ export interface StorybookConfigureSchema { js?: boolean; linter?: Linter; cypressDirectory?: string; + standaloneConfig?: boolean; } diff --git a/packages/react/src/generators/storybook-configuration/schema.json b/packages/react/src/generators/storybook-configuration/schema.json index 05b111f6c3..a6f1d24ec1 100644 --- a/packages/react/src/generators/storybook-configuration/schema.json +++ b/packages/react/src/generators/storybook-configuration/schema.json @@ -44,6 +44,11 @@ "type": "string", "enum": ["eslint", "tslint"], "default": "eslint" + }, + "standaloneConfig": { + "description": "Split the project configuration into /project.json rather than including it inside workspace.json", + "type": "boolean", + "default": false } }, "required": ["name"] diff --git a/packages/react/src/generators/storybook-migrate-defaults-5-to-6/migrate-defaults-5-to-6.spec.ts b/packages/react/src/generators/storybook-migrate-defaults-5-to-6/migrate-defaults-5-to-6.spec.ts index 31238c30ea..40784369b1 100644 --- a/packages/react/src/generators/storybook-migrate-defaults-5-to-6/migrate-defaults-5-to-6.spec.ts +++ b/packages/react/src/generators/storybook-migrate-defaults-5-to-6/migrate-defaults-5-to-6.spec.ts @@ -28,6 +28,7 @@ describe('migrate-defaults-5-to-6 schematic', () => { configureCypress: false, generateCypressSpecs: false, generateStories: false, + standaloneConfig: false, }); jest.spyOn(logger, 'warn').mockImplementation(() => {}); diff --git a/packages/react/src/utils/testing-generators.ts b/packages/react/src/utils/testing-generators.ts index 5db546ce31..152ae6f28f 100644 --- a/packages/react/src/utils/testing-generators.ts +++ b/packages/react/src/utils/testing-generators.ts @@ -3,7 +3,11 @@ import applicationGenerator from '../generators/application/application'; import { Linter } from '@nrwl/linter'; import { applicationGenerator as webApplicationGenerator } from '@nrwl/web'; -export async function createApp(tree: Tree, appName: string): Promise { +export async function createApp( + tree: Tree, + appName: string, + standaloneConfig?: boolean +): Promise { const { fileName } = names(appName); await applicationGenerator(tree, { @@ -14,28 +18,43 @@ export async function createApp(tree: Tree, appName: string): Promise { style: 'css', unitTestRunner: 'none', name: appName, + standaloneConfig, }); } -export async function createWebApp(tree: Tree, appName: string): Promise { +export async function createWebApp( + tree: Tree, + appName: string, + standaloneConfig?: boolean +): Promise { const { fileName } = names(appName); await webApplicationGenerator(tree, { name: appName, skipFormat: true, + standaloneConfig, }); } -export async function createLib(tree: Tree, libName: string): Promise { +export async function createLib( + tree: Tree, + libName: string, + standaloneConfig?: boolean +): Promise { const { fileName } = names(libName); tree.write(`/libs/${fileName}/src/index.ts`, `import React from 'react';\n`); - addProjectConfiguration(tree, fileName, { - tags: [], - root: `libs/${fileName}`, - projectType: 'library', - sourceRoot: `libs/${fileName}/src`, - targets: {}, - }); + addProjectConfiguration( + tree, + fileName, + { + tags: [], + root: `libs/${fileName}`, + projectType: 'library', + sourceRoot: `libs/${fileName}/src`, + targets: {}, + }, + standaloneConfig + ); } diff --git a/packages/storybook/src/generators/configuration/configuration.spec.ts b/packages/storybook/src/generators/configuration/configuration.spec.ts index 06c51e678d..94c5dc8bd8 100644 --- a/packages/storybook/src/generators/configuration/configuration.spec.ts +++ b/packages/storybook/src/generators/configuration/configuration.spec.ts @@ -20,6 +20,7 @@ describe('@nrwl/storybook:configuration', () => { tree = createTreeWithEmptyWorkspace(); await libraryGenerator(tree, { name: 'test-ui-lib', + standaloneConfig: false, }); writeJson(tree, 'package.json', { devDependencies: { @@ -33,6 +34,7 @@ describe('@nrwl/storybook:configuration', () => { await configurationGenerator(tree, { name: 'test-ui-lib', uiFramework: '@storybook/angular', + standaloneConfig: false, }); // Root @@ -79,6 +81,7 @@ describe('@nrwl/storybook:configuration', () => { await configurationGenerator(tree, { name: 'test-ui-lib', uiFramework: '@storybook/angular', + standaloneConfig: false, }); const newContents = `module.exports = { @@ -89,12 +92,14 @@ describe('@nrwl/storybook:configuration', () => { // Setup a new lib await libraryGenerator(tree, { name: 'test-ui-lib-2', + standaloneConfig: false, }); tree.write('.storybook/main.js', newContents); await configurationGenerator(tree, { name: 'test-ui-lib-2', uiFramework: '@storybook/angular', + standaloneConfig: false, }); expect(tree.read('.storybook/main.js', 'utf-8')).toEqual(newContents); @@ -104,6 +109,7 @@ describe('@nrwl/storybook:configuration', () => { await configurationGenerator(tree, { name: 'test-ui-lib', uiFramework: '@storybook/react', + standaloneConfig: false, }); const project = readProjectConfiguration(tree, 'test-ui-lib'); @@ -135,6 +141,7 @@ describe('@nrwl/storybook:configuration', () => { await configurationGenerator(tree, { name: 'test-ui-lib', uiFramework: '@storybook/react', + standaloneConfig: false, }); const tsconfigJson = readJson( tree, @@ -151,6 +158,7 @@ describe('@nrwl/storybook:configuration', () => { await configurationGenerator(tree, { name: 'test-ui-lib', uiFramework: '@storybook/react', + standaloneConfig: false, }); const tsconfigJson = readJson( tree, @@ -176,6 +184,7 @@ describe('@nrwl/storybook:configuration', () => { await libraryGenerator(tree, { name: 'test-ui-lib2', linter: Linter.EsLint, + standaloneConfig: false, }); updateJson(tree, 'libs/test-ui-lib2/.eslintrc.json', (json) => { @@ -188,6 +197,7 @@ describe('@nrwl/storybook:configuration', () => { await configurationGenerator(tree, { name: 'test-ui-lib2', uiFramework: '@storybook/react', + standaloneConfig: false, }); expect(readJson(tree, 'libs/test-ui-lib2/.eslintrc.json').parserOptions) diff --git a/packages/storybook/src/generators/configuration/configuration.ts b/packages/storybook/src/generators/configuration/configuration.ts index bf2154e04b..0506731826 100644 --- a/packages/storybook/src/generators/configuration/configuration.ts +++ b/packages/storybook/src/generators/configuration/configuration.ts @@ -61,6 +61,7 @@ export async function configurationGenerator( js: schema.js, linter: schema.linter, directory: schema.cypressDirectory, + standaloneConfig: schema.standaloneConfig, }); tasks.push(cypressTask); } else { diff --git a/packages/storybook/src/generators/configuration/schema.d.ts b/packages/storybook/src/generators/configuration/schema.d.ts index 7473ce9ea7..1c54b809e1 100644 --- a/packages/storybook/src/generators/configuration/schema.d.ts +++ b/packages/storybook/src/generators/configuration/schema.d.ts @@ -7,4 +7,5 @@ export interface StorybookConfigureSchema { linter?: Linter; js?: boolean; cypressDirectory?: string; + standaloneConfig?: boolean; } diff --git a/packages/storybook/src/generators/configuration/schema.json b/packages/storybook/src/generators/configuration/schema.json index 53fabe08bf..430b77b75a 100644 --- a/packages/storybook/src/generators/configuration/schema.json +++ b/packages/storybook/src/generators/configuration/schema.json @@ -37,6 +37,11 @@ "type": "boolean", "description": "Generate JavaScript files rather than TypeScript files", "default": false + }, + "standaloneConfig": { + "description": "Split the project configuration into /project.json rather than including it inside workspace.json", + "type": "boolean", + "default": false } }, "required": ["name"] diff --git a/packages/storybook/src/generators/cypress-project/cypress-project.spec.ts b/packages/storybook/src/generators/cypress-project/cypress-project.spec.ts index 0f1047f892..73212c4b22 100644 --- a/packages/storybook/src/generators/cypress-project/cypress-project.spec.ts +++ b/packages/storybook/src/generators/cypress-project/cypress-project.spec.ts @@ -11,6 +11,7 @@ describe('@nrwl/storybook:cypress-project', () => { tree = createTreeWithEmptyWorkspace(); await libraryGenerator(tree, { name: 'test-ui-lib', + standaloneConfig: false, }); }); @@ -18,6 +19,7 @@ describe('@nrwl/storybook:cypress-project', () => { await cypressProjectGenerator(tree, { name: 'test-ui-lib', linter: Linter.EsLint, + standaloneConfig: false, }); expect(tree.exists('apps/test-ui-lib-e2e/cypress.json')).toBeTruthy(); @@ -29,6 +31,7 @@ describe('@nrwl/storybook:cypress-project', () => { await cypressProjectGenerator(tree, { name: 'test-ui-lib', linter: Linter.EsLint, + standaloneConfig: false, }); const project = readProjectConfiguration(tree, 'test-ui-lib-e2e'); @@ -47,6 +50,7 @@ describe('@nrwl/storybook:cypress-project', () => { name: 'test-ui-lib', directory: 'one/two', linter: Linter.EsLint, + standaloneConfig: false, }); const workspace = readJson(tree, 'workspace.json'); expect(workspace.projects['one-two-test-ui-lib-e2e']).toBeDefined(); diff --git a/packages/storybook/src/generators/cypress-project/cypress-project.ts b/packages/storybook/src/generators/cypress-project/cypress-project.ts index e9028b13c2..916cad9783 100644 --- a/packages/storybook/src/generators/cypress-project/cypress-project.ts +++ b/packages/storybook/src/generators/cypress-project/cypress-project.ts @@ -20,6 +20,7 @@ export interface CypressConfigureSchema { js?: boolean; directory?: string; linter: Linter; + standaloneConfig?: boolean; } export async function cypressProjectGenerator( @@ -37,6 +38,7 @@ export async function cypressProjectGenerator( js: schema.js, linter: schema.linter, directory: schema.directory, + standaloneConfig: schema.standaloneConfig, }); const generatedCypressProjectName = getE2eProjectName( schema.name, diff --git a/packages/storybook/src/generators/cypress-project/schema.json b/packages/storybook/src/generators/cypress-project/schema.json index 8f209f9461..cd6c5ac4fe 100644 --- a/packages/storybook/src/generators/cypress-project/schema.json +++ b/packages/storybook/src/generators/cypress-project/schema.json @@ -26,6 +26,11 @@ "type": "string", "enum": ["eslint", "tslint", "none"], "default": "eslint" + }, + "standaloneConfig": { + "description": "Split the project configuration into /project.json rather than including it inside workspace.json", + "type": "boolean", + "default": false } }, "required": ["name"] diff --git a/packages/tao/src/commands/ngcli-adapter.ts b/packages/tao/src/commands/ngcli-adapter.ts index 56ecbf5479..2b8c046300 100644 --- a/packages/tao/src/commands/ngcli-adapter.ts +++ b/packages/tao/src/commands/ngcli-adapter.ts @@ -17,14 +17,16 @@ import { detectPackageManager } from '@nrwl/tao/src/shared/package-manager'; import { GenerateOptions } from './generate'; import { Tree } from '../shared/tree'; import { + inlineProjectConfigurations, + toNewFormat, toNewFormatOrNull, toOldFormatOrNull, workspaceConfigName, } from '@nrwl/tao/src/shared/workspace'; import { dirname, extname, resolve, join } from 'path'; import { FileBuffer } from '@angular-devkit/core/src/virtual-fs/host/interface'; -import { Observable, of } from 'rxjs'; -import { catchError, map, switchMap } from 'rxjs/operators'; +import { EMPTY, Observable, of, concat } from 'rxjs'; +import { catchError, map, switchMap, tap, toArray } from 'rxjs/operators'; import { NX_ERROR, NX_PREFIX } from '../shared/logger'; import { readJsonFile } from '../utils/fileutils'; import { parseJson, serializeJson } from '../utils/json'; @@ -197,18 +199,31 @@ export class NxScopedHost extends virtualFs.ScopedHost { if (r.isWorkspaceConfig) { if (r.isNewFormat) { return super.read(r.actualConfigFileName).pipe( - map((r) => { + switchMap((r) => { try { const w = parseJson(Buffer.from(r).toString()); - const formatted = toOldFormatOrNull(w); - return formatted ? Buffer.from(serializeJson(formatted)) : r; - } catch { - return r; + return this.resolveInlineProjectConfigurations(w).pipe( + map((w) => { + const formatted = toOldFormatOrNull(w); + return formatted + ? Buffer.from(serializeJson(formatted)) + : Buffer.from(serializeJson(w)); + }) + ); + } catch (ex) { + return of(r); } }) ); } else { - return super.read(r.actualConfigFileName); + return super.read(r.actualConfigFileName).pipe( + map((r) => { + const w = parseJson(Buffer.from(r).toString()); + return Buffer.from( + serializeJson(inlineProjectConfigurations(w)) + ); + }) + ); } } else { return super.read(path); @@ -221,24 +236,7 @@ export class NxScopedHost extends virtualFs.ScopedHost { return this.context(path).pipe( switchMap((r) => { if (r.isWorkspaceConfig) { - if (r.isNewFormat) { - try { - const w = parseJson(Buffer.from(content).toString()); - const formatted = toNewFormatOrNull(w); - if (formatted) { - return super.write( - r.actualConfigFileName, - Buffer.from(serializeJson(formatted)) - ); - } else { - return super.write(r.actualConfigFileName, content); - } - } catch (e) { - return super.write(r.actualConfigFileName, content); - } - } else { - return super.write(r.actualConfigFileName, content); - } + return this.writeWorkspaceConfiguration(r, content); } else { return super.write(path, content); } @@ -320,6 +318,84 @@ export class NxScopedHost extends virtualFs.ScopedHost { }); } } + + private writeWorkspaceConfiguration(context, content): Observable { + const config = parseJson(Buffer.from(content).toString()); + if (context.isNewFormat) { + try { + const w = parseJson(Buffer.from(content).toString()); + const formatted = toNewFormatOrNull(w); + if (formatted) { + return this.writeWorkspaceConfigFiles( + context.actualConfigFileName, + formatted + ); + } else { + return this.writeWorkspaceConfigFiles( + context.actualConfigFileName, + config + ); + } + } catch (e) { + return this.writeWorkspaceConfigFiles( + context.actualConfigFileName, + config + ); + } + } else { + return this.writeWorkspaceConfigFiles( + context.actualConfigFileName, + config + ); + } + } + + private writeWorkspaceConfigFiles(workspaceFileName, config) { + Object.entries(config.projects as Record).forEach( + ([project, config]) => { + if (config.configFilePath) { + const configPath = config.configFilePath; + const fileConfigObject = { ...config }; + delete fileConfigObject.configFilePath; + super.write(configPath, Buffer.from(serializeJson(fileConfigObject))); + config.projects[project] = dirname(configPath); + } + } + ); + return super.write(workspaceFileName, Buffer.from(serializeJson(config))); + } + + protected resolveInlineProjectConfigurations(config: { + projects: Record; + }): Observable { + let observable: Observable = EMPTY; + Object.entries((config.projects as Record) ?? {}).forEach( + ([project, projectConfig]) => { + if (typeof projectConfig === 'string') { + const configFilePath = `${projectConfig}/project.json`; + const next = super.read(configFilePath as Path).pipe( + map((x) => ({ + project, + projectConfig: { + ...parseJson(Buffer.from(x).toString()), + configFilePath, + }, + })) + ); + observable = observable ? concat(observable, next) : next; + } + } + ); + return observable.pipe( + toArray(), + map((x: any[]) => { + x.forEach(({ project, projectConfig }) => { + config.projects[project] = projectConfig; + }); + return config; + }) + ); + } } /** @@ -363,11 +439,13 @@ export class NxScopeHostUsedForWrappedSchematics extends NxScopedHost { // we try to format it, if it changes, return it, otherwise return the original change try { const w = parseJson(Buffer.from(match.content).toString()); - const formatted = toOldFormatOrNull(w); - return of( - formatted - ? Buffer.from(serializeJson(formatted)) - : Buffer.from(match.content) + return this.resolveInlineProjectConfigurations(w).pipe( + map((x) => { + const formatted = toOldFormatOrNull(w); + return formatted + ? Buffer.from(serializeJson(formatted)) + : Buffer.from(serializeJson(x)); + }) ); } catch (e) { return super.read(path); @@ -646,7 +724,7 @@ function convertEventTypeToHandleMultipleConfigNames( } catch (e) {} if (content && isNewFormat) { - const formatted = toNewFormatOrNull(parseJson(content.toString())); + const formatted = toNewFormat(parseJson(content.toString())); if (formatted) { return { eventPath: actualConfigName, diff --git a/packages/tao/src/shared/workspace.spec.ts b/packages/tao/src/shared/workspace.spec.ts new file mode 100644 index 0000000000..3e78a4c836 --- /dev/null +++ b/packages/tao/src/shared/workspace.spec.ts @@ -0,0 +1,40 @@ +import { inlineProjectConfigurations } from './workspace'; +import { readJsonFile } from '../utils/fileutils'; + +jest.mock('../utils/fileutils'); + +const libConfig = (name) => ({ + root: `libs/${name}`, + sourceRoot: `libs/${name}/src`, + targets: {}, +}); + +describe('workspace', () => { + it('should be able to inline project configurations', () => { + const standaloneConfig = libConfig('lib1'); + + (readJsonFile as jest.Mock).mockImplementation((path) => { + if (path === 'libs/lib1/project.json') { + return standaloneConfig; + } + throw `${path} not in mock!`; + }); + + const inlineConfig = { + version: 1, + projects: { + lib1: 'libs/lib1', + lib2: libConfig('lib2'), + }, + }; + + const resolved = inlineProjectConfigurations(inlineConfig); + expect(resolved).toEqual({ + ...inlineConfig, + projects: { + ...inlineConfig.projects, + lib1: { ...standaloneConfig, configFilePath: 'libs/lib1/project.json' }, + }, + }); + }); +}); diff --git a/packages/tao/src/shared/workspace.ts b/packages/tao/src/shared/workspace.ts index 4e7eae3850..31fb094000 100644 --- a/packages/tao/src/shared/workspace.ts +++ b/packages/tao/src/shared/workspace.ts @@ -57,6 +57,11 @@ export interface WorkspaceJsonConfiguration { }; } +export interface RawWorkspaceJsonConfiguration + extends Omit { + projects: { [projectName: string]: ProjectConfiguration | string }; +} + /** * Type of project supported */ @@ -264,7 +269,7 @@ export class Workspaces { const w = readJsonFile( path.join(this.root, workspaceConfigName(this.root)) ); - return toNewFormat(w); + return resolveNewFormatWithInlineProjects(w); } isNxExecutor(nodeModule: string, executor: string) { @@ -458,25 +463,24 @@ export function toNewFormat(w: any): WorkspaceJsonConfiguration { export function toNewFormatOrNull(w: any) { let formatted = false; - Object.values(w.projects || {}).forEach((project: any) => { - if (project.architect) { - renameProperty(project, 'architect', 'targets'); + Object.values(w.projects || {}).forEach((projectConfig: any) => { + if (projectConfig.architect) { + renamePropertyWithStableKeys(projectConfig, 'architect', 'targets'); formatted = true; } - if (project.schematics) { - renameProperty(project, 'schematics', 'generators'); + if (projectConfig.schematics) { + renamePropertyWithStableKeys(projectConfig, 'schematics', 'generators'); formatted = true; } - Object.values(project.targets || {}).forEach((target: any) => { + Object.values(projectConfig.targets || {}).forEach((target: any) => { if (target.builder) { - renameProperty(target, 'builder', 'executor'); + renamePropertyWithStableKeys(target, 'builder', 'executor'); formatted = true; } }); }); - if (w.schematics) { - renameProperty(w, 'schematics', 'generators'); + renamePropertyWithStableKeys(w, 'schematics', 'generators'); formatted = true; } if (w.version !== 2) { @@ -488,25 +492,31 @@ export function toNewFormatOrNull(w: any) { export function toOldFormatOrNull(w: any) { let formatted = false; - Object.values(w.projects || {}).forEach((project: any) => { - if (project.targets) { - renameProperty(project, 'targets', 'architect'); + + Object.values(w.projects || {}).forEach((projectConfig: any) => { + if (typeof projectConfig === 'string') { + throw new Error( + "'project.json' files are incompatible with version 1 workspace schemas." + ); + } + if (projectConfig.targets) { + renamePropertyWithStableKeys(projectConfig, 'targets', 'architect'); formatted = true; } - if (project.generators) { - renameProperty(project, 'generators', 'schematics'); + if (projectConfig.generators) { + renamePropertyWithStableKeys(projectConfig, 'generators', 'schematics'); formatted = true; } - Object.values(project.architect || {}).forEach((target: any) => { + Object.values(projectConfig.architect || {}).forEach((target: any) => { if (target.executor) { - renameProperty(target, 'executor', 'builder'); + renamePropertyWithStableKeys(target, 'executor', 'builder'); formatted = true; } }); }); if (w.generators) { - renameProperty(w, 'generators', 'schematics'); + renamePropertyWithStableKeys(w, 'generators', 'schematics'); formatted = true; } if (w.version !== 1) { @@ -516,9 +526,34 @@ export function toOldFormatOrNull(w: any) { return formatted ? w : null; } +export function resolveOldFormatWithInlineProjects(w: any) { + return toOldFormatOrNull(inlineProjectConfigurations(w)); +} + +export function resolveNewFormatWithInlineProjects(w: any) { + return toNewFormat(inlineProjectConfigurations(w)); +} + +export function inlineProjectConfigurations(w: any) { + Object.entries(w.projects || {}).forEach( + ([project, config]: [string, any]) => { + if (typeof config === 'string') { + const configFilePath = path.join(config, 'project.json'); + const fileConfig = readJsonFile(configFilePath); + w.projects[project] = { ...fileConfig, configFilePath }; + } + } + ); + return w; +} + // we have to do it this way to preserve the order of properties // not to screw up the formatting -function renameProperty(obj: any, from: string, to: string) { +export function renamePropertyWithStableKeys( + obj: any, + from: string, + to: string +) { const copy = { ...obj }; Object.keys(obj).forEach((k) => { delete obj[k]; diff --git a/packages/web/src/generators/application/application.spec.ts b/packages/web/src/generators/application/application.spec.ts index e9fbb2acb0..60a64fe18f 100644 --- a/packages/web/src/generators/application/application.spec.ts +++ b/packages/web/src/generators/application/application.spec.ts @@ -14,7 +14,10 @@ describe('app', () => { describe('not nested', () => { it('should update workspace.json', async () => { - await applicationGenerator(tree, { name: 'myApp' }); + await applicationGenerator(tree, { + name: 'myApp', + standaloneConfig: false, + }); const workspaceJson = readJson(tree, '/workspace.json'); expect(workspaceJson.projects['my-app'].root).toEqual('apps/my-app'); @@ -25,7 +28,11 @@ describe('app', () => { }); it('should update nx.json', async () => { - await applicationGenerator(tree, { name: 'myApp', tags: 'one,two' }); + await applicationGenerator(tree, { + name: 'myApp', + tags: 'one,two', + standaloneConfig: false, + }); const nxJson = readJson(tree, '/nx.json'); expect(nxJson.projects).toEqual({ 'my-app': { @@ -39,7 +46,10 @@ describe('app', () => { }); it('should generate files', async () => { - await applicationGenerator(tree, { name: 'myApp' }); + await applicationGenerator(tree, { + name: 'myApp', + standaloneConfig: false, + }); expect(tree.exists('apps/my-app/src/main.ts')).toBeTruthy(); expect(tree.exists('apps/my-app/src/app/app.element.ts')).toBeTruthy(); expect( @@ -110,6 +120,7 @@ describe('app', () => { await applicationGenerator(tree, { name: 'myApp', directory: 'myDir', + standaloneConfig: false, }); const workspaceJson = readJson(tree, '/workspace.json'); @@ -126,6 +137,7 @@ describe('app', () => { name: 'myApp', directory: 'myDir', tags: 'one,two', + standaloneConfig: false, }); const nxJson = readJson(tree, '/nx.json'); expect(nxJson.projects).toEqual({ @@ -148,6 +160,7 @@ describe('app', () => { await applicationGenerator(tree, { name: 'myApp', directory: 'myDir', + standaloneConfig: false, }); // Make sure these exist @@ -184,6 +197,7 @@ describe('app', () => { await applicationGenerator(tree, { name: 'myApp', directory: 'myDir', + standaloneConfig: false, }); expect( tree.read('apps/my-dir/my-app/src/app/app.element.ts', 'utf-8') @@ -199,6 +213,7 @@ describe('app', () => { await applicationGenerator(tree, { name: 'myApp', style: 'scss', + standaloneConfig: false, }); expect(tree.exists('apps/my-app/src/app/app.element.scss')).toEqual(true); }); @@ -207,6 +222,7 @@ describe('app', () => { it('should setup jest without serializers', async () => { await applicationGenerator(tree, { name: 'my-App', + standaloneConfig: false, }); expect(tree.read('apps/my-app/jest.config.js', 'utf-8')).not.toContain( @@ -217,6 +233,7 @@ describe('app', () => { it('should setup the nrwl web build builder', async () => { await applicationGenerator(tree, { name: 'my-App', + standaloneConfig: false, }); const workspaceJson = readJson(tree, 'workspace.json'); const architectConfig = workspaceJson.projects['my-app'].architect; @@ -259,6 +276,7 @@ describe('app', () => { it('should setup the nrwl web dev server builder', async () => { await applicationGenerator(tree, { name: 'my-App', + standaloneConfig: false, }); const workspaceJson = readJson(tree, 'workspace.json'); const architectConfig = workspaceJson.projects['my-app'].architect; @@ -274,6 +292,7 @@ describe('app', () => { it('should setup the eslint builder', async () => { await applicationGenerator(tree, { name: 'my-App', + standaloneConfig: false, }); const workspaceJson = readJson(tree, 'workspace.json'); @@ -287,7 +306,11 @@ describe('app', () => { describe('--prefix', () => { it('should use the prefix in the index.html', async () => { - await applicationGenerator(tree, { name: 'myApp', prefix: 'prefix' }); + await applicationGenerator(tree, { + name: 'myApp', + prefix: 'prefix', + standaloneConfig: false, + }); expect(tree.read('apps/my-app/src/index.html', 'utf-8')).toContain( '' @@ -300,6 +323,7 @@ describe('app', () => { await applicationGenerator(tree, { name: 'myApp', unitTestRunner: 'none', + standaloneConfig: false, }); expect(tree.exists('jest.config.js')).toBeFalsy(); expect( @@ -328,6 +352,7 @@ describe('app', () => { await applicationGenerator(tree, { name: 'myApp', e2eTestRunner: 'none', + standaloneConfig: false, }); expect(tree.exists('apps/my-app-e2e')).toBeFalsy(); const workspaceJson = readJson(tree, 'workspace.json'); diff --git a/packages/web/src/generators/application/application.ts b/packages/web/src/generators/application/application.ts index 8b2a3edbaf..8b7f15f484 100644 --- a/packages/web/src/generators/application/application.ts +++ b/packages/web/src/generators/application/application.ts @@ -149,7 +149,12 @@ function addProject(tree: Tree, options: NormalizedSchema) { project = addBuildTarget(project, options); project = addServeTarget(project, options); - addProjectConfiguration(tree, options.projectName, project); + addProjectConfiguration( + tree, + options.projectName, + project, + options.standaloneConfig + ); const workspace = readWorkspaceConfiguration(tree); diff --git a/packages/web/src/generators/application/schema.d.ts b/packages/web/src/generators/application/schema.d.ts index 2e6e69d38d..1224ee48be 100644 --- a/packages/web/src/generators/application/schema.d.ts +++ b/packages/web/src/generators/application/schema.d.ts @@ -11,4 +11,5 @@ export interface Schema { e2eTestRunner?: 'cypress' | 'none'; linter?: Linter; babelJest?: boolean; + standaloneConfig?: boolean; } diff --git a/packages/web/src/generators/application/schema.json b/packages/web/src/generators/application/schema.json index 742d9b87dc..bb59a55980 100644 --- a/packages/web/src/generators/application/schema.json +++ b/packages/web/src/generators/application/schema.json @@ -74,6 +74,11 @@ "type": "boolean", "description": "Use babel instead ts-jest", "default": false + }, + "standaloneConfig": { + "description": "Split the project configuration into /project.json rather than including it inside workspace.json", + "type": "boolean", + "default": false } }, "required": [] diff --git a/packages/workspace/collection.json b/packages/workspace/collection.json index fe67dcfba6..eb5362ee14 100644 --- a/packages/workspace/collection.json +++ b/packages/workspace/collection.json @@ -127,6 +127,12 @@ "schema": "./src/generators/run-commands/schema.json", "aliases": ["run-command", "target"], "description": "Generates a target to run any command in the terminal" + }, + + "convert-to-nx-project": { + "factory": "./src/generators/convert-to-nx-project/convert-to-nx-project#convertToNxProjectGenerator", + "schema": "./src/generators/convert-to-nx-project/schema.json", + "description": "Moves a project's configuration outside of workspace.json" } } } diff --git a/packages/workspace/index.ts b/packages/workspace/index.ts index bd64a35235..d153767b28 100644 --- a/packages/workspace/index.ts +++ b/packages/workspace/index.ts @@ -93,5 +93,9 @@ export { libraryGenerator } from './src/generators/library/library'; export { moveGenerator } from './src/generators/move/move'; export { removeGenerator } from './src/generators/remove/remove'; export { runCommandsGenerator } from './src/generators/run-commands/run-commands'; +export { + convertToNxProjectGenerator, + convertToNxProjectSchematic, +} from './src/generators/convert-to-nx-project/convert-to-nx-project'; export const stringUtils = strings; diff --git a/packages/workspace/src/core/assert-workspace-validity.spec.ts b/packages/workspace/src/core/assert-workspace-validity.spec.ts index 55ab5f9654..9e0c50ab65 100644 --- a/packages/workspace/src/core/assert-workspace-validity.spec.ts +++ b/packages/workspace/src/core/assert-workspace-validity.spec.ts @@ -66,25 +66,25 @@ describe('assertWorkspaceValidity', () => { mockExit.mockRestore(); }); - it('should throw for a missing project in nx.json', () => { - jest.spyOn(output, 'error'); + // it('should throw for a missing project in nx.json', () => { + // jest.spyOn(output, 'error'); - delete mockNxJson.projects.app1; + // delete mockNxJson.projects.app1; - const mockExit = jest - .spyOn(process, 'exit') - .mockImplementation(((code?: number) => {}) as any); - assertWorkspaceValidity(mockWorkspaceJson, mockNxJson); + // const mockExit = jest + // .spyOn(process, 'exit') + // .mockImplementation(((code?: number) => {}) as any); + // assertWorkspaceValidity(mockWorkspaceJson, mockNxJson); - expect(mockExit).toHaveBeenCalledWith(1); - expect(output.error).toHaveBeenCalledWith({ - title: 'Configuration Error', - bodyLines: [ - `workspace.json and nx.json are out of sync. The following projects are missing in nx.json: app1`, - ], - }); - mockExit.mockRestore(); - }); + // expect(mockExit).toHaveBeenCalledWith(1); + // expect(output.error).toHaveBeenCalledWith({ + // title: 'Configuration Error', + // bodyLines: [ + // `workspace.json and nx.json are out of sync. The following projects are missing in nx.json: app1`, + // ], + // }); + // mockExit.mockRestore(); + // }); it('should throw for an invalid top-level implicit dependency', () => { jest.spyOn(output, 'error'); diff --git a/packages/workspace/src/core/assert-workspace-validity.ts b/packages/workspace/src/core/assert-workspace-validity.ts index 6dec8b586b..4043485ad8 100644 --- a/packages/workspace/src/core/assert-workspace-validity.ts +++ b/packages/workspace/src/core/assert-workspace-validity.ts @@ -12,20 +12,6 @@ export function assertWorkspaceValidity( const workspaceJsonProjects = Object.keys(workspaceJson.projects); const nxJsonProjects = Object.keys(nxJson.projects); - if (minus(workspaceJsonProjects, nxJsonProjects).length > 0) { - output.error({ - title: 'Configuration Error', - bodyLines: [ - `${workspaceFileName()} and nx.json are out of sync. The following projects are missing in nx.json: ${minus( - workspaceJsonProjects, - nxJsonProjects - ).join(', ')}`, - ], - }); - - process.exit(1); - } - if (minus(nxJsonProjects, workspaceJsonProjects).length > 0) { output.error({ title: 'Configuration Error', diff --git a/packages/workspace/src/core/file-utils.ts b/packages/workspace/src/core/file-utils.ts index 0c1b4008bb..d69bbb7687 100644 --- a/packages/workspace/src/core/file-utils.ts +++ b/packages/workspace/src/core/file-utils.ts @@ -2,6 +2,7 @@ import { toOldFormatOrNull, Workspaces } from '@nrwl/tao/src/shared/workspace'; import type { FileData, NxJsonConfiguration, + NxJsonProjectConfiguration, ProjectGraphNode, } from '@nrwl/devkit'; import { execSync } from 'child_process'; @@ -202,6 +203,20 @@ export function readNxJson(): NxJsonConfiguration { if (!config.npmScope) { throw new Error(`nx.json must define the npmScope property.`); } + + // NOTE: As we work towards removing nx.json, some settings are now found in + // the workspace.json file. Currently this is only supported for projects + // with separated configs, as they list tags / implicit deps inside the project.json file. + const workspace = readWorkspaceConfig({ format: 'nx', path: appRootPath }); + Object.entries(workspace.projects).forEach( + ([project, projectConfig]: [string, NxJsonProjectConfiguration]) => { + if (!config.projects[project]) { + const { tags, implicitDependencies } = projectConfig; + config.projects[project] = { tags, implicitDependencies }; + } + } + ); + return config; } diff --git a/packages/workspace/src/core/hasher/hasher.spec.ts b/packages/workspace/src/core/hasher/hasher.spec.ts index 69e6bf18e2..82843b40b6 100644 --- a/packages/workspace/src/core/hasher/hasher.spec.ts +++ b/packages/workspace/src/core/hasher/hasher.spec.ts @@ -27,7 +27,7 @@ describe('Hasher', () => { fs.readFileSync = (file) => { if (file === 'workspace.json') { return JSON.stringify({ - projects: { proj: 'proj-from-workspace.json' }, + projects: { proj: { root: 'proj-from-workspace.json' } }, }); } if (file === 'nx.json') { @@ -75,7 +75,8 @@ describe('Hasher', () => { expect(hash.details.command).toEqual('proj|build||{"prop":"prop-value"}'); expect(hash.details.nodes).toEqual({ - proj: '/file|file.hash|"proj-from-workspace.json"|"proj-from-nx.json"', + proj: + '/file|file.hash|{"root":"proj-from-workspace.json"}|"proj-from-nx.json"', }); expect(hash.details.implicitDeps).toEqual({ 'yarn.lock': 'yarn.lock.hash', @@ -125,8 +126,10 @@ describe('Hasher', () => { expect(e.message).toContain( 'Nx failed to execute runtimeCacheInputs defined in nx.json failed:' ); - expect(e.message).toContain('boom:'); - expect(e.message).toContain(' not found'); + expect(e.message).toContain('boom'); + expect( + e.message.includes(' not found') || e.message.includes('not recognized') + ).toBeTruthy(); } }); diff --git a/packages/workspace/src/core/hasher/hasher.ts b/packages/workspace/src/core/hasher/hasher.ts index 9d27967b6f..f3c267ebb4 100644 --- a/packages/workspace/src/core/hasher/hasher.ts +++ b/packages/workspace/src/core/hasher/hasher.ts @@ -11,6 +11,7 @@ import { ProjectGraph, WorkspaceJsonConfiguration, } from '@nrwl/devkit'; +import { resolveNewFormatWithInlineProjects } from '@nrwl/tao/src/shared/workspace'; export interface Hash { value: string; @@ -273,8 +274,8 @@ class ProjectHasher { private readonly projectGraph: ProjectGraph, private readonly hashing: HashingImpl ) { - this.workspaceJson = this.readConfigFile(workspaceFileName()); - this.nxJson = this.readConfigFile('nx.json'); + this.workspaceJson = this.readWorkspaceConfigFile(workspaceFileName()); + this.nxJson = this.readNxJsonConfigFile('nx.json'); } async hashProject( @@ -335,13 +336,23 @@ class ProjectHasher { return this.sourceHashes[projectName]; } - private readConfigFile(path: string) { + private readWorkspaceConfigFile(path: string): WorkspaceJsonConfiguration { + try { + const res = readJsonFile(path); + res.projects ??= {}; + return resolveNewFormatWithInlineProjects(res); + } catch { + return { projects: {}, version: 2 }; + } + } + + private readNxJsonConfigFile(path: string): NxJsonConfiguration { try { const res = readJsonFile(path); res.projects ??= {}; return res; } catch { - return { projects: {} }; + return { projects: {}, npmScope: '' }; } } } diff --git a/packages/workspace/src/generators/convert-to-nx-project/convert-to-nx-project.spec.ts b/packages/workspace/src/generators/convert-to-nx-project/convert-to-nx-project.spec.ts new file mode 100644 index 0000000000..e7008e980d --- /dev/null +++ b/packages/workspace/src/generators/convert-to-nx-project/convert-to-nx-project.spec.ts @@ -0,0 +1,150 @@ +import { + NxJsonProjectConfiguration, + readJson, + readProjectConfiguration, +} from '@nrwl/devkit'; +import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; +import enquirer = require('enquirer'); +import { libraryGenerator } from '../library/library'; + +import convertToNxProject, { + SCHEMA_OPTIONS_ARE_MUTUALLY_EXCLUSIVE, +} from './convert-to-nx-project'; +import { getProjectConfigurationPath } from './utils/get-project-configuration-path'; + +jest.mock('fs-extra', () => ({ + ...jest.requireActual('fs-extra'), + readJsonSync: () => ({}), +})); + +jest.mock('enquirer', () => ({ + prompt: () => ({ + project: 'lib', + }), +})); + +describe('convert-to-nx-project', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('should throw if project && all are both specified', async () => { + const tree = createTreeWithEmptyWorkspace(2); + + await libraryGenerator(tree, { + name: 'lib', + standaloneConfig: false, + }); + + const p = convertToNxProject(tree, { all: true, project: 'lib' }); + await expect(p).rejects.toMatch(SCHEMA_OPTIONS_ARE_MUTUALLY_EXCLUSIVE); + }); + + it('should prompt for a project if neither project nor all are specified', async () => { + const spy = jest.spyOn(enquirer, 'prompt'); + + const tree = createTreeWithEmptyWorkspace(2); + + await libraryGenerator(tree, { + name: 'lib', + standaloneConfig: false, + }); + + const p = await convertToNxProject(tree, {}); + expect(spy).toHaveBeenCalledTimes(1); + }); + + it('should not prompt for a project if all is specified', async () => { + const spy = jest.spyOn(enquirer, 'prompt'); + + const tree = createTreeWithEmptyWorkspace(2); + + await libraryGenerator(tree, { + name: 'lib', + standaloneConfig: false, + }); + + const p = await convertToNxProject(tree, { all: true }); + expect(spy).toHaveBeenCalledTimes(0); + }); + + it('should extract single project configuration to project.json', async () => { + const tree = createTreeWithEmptyWorkspace(2); + + await libraryGenerator(tree, { + name: 'lib', + standaloneConfig: false, + }); + + const config = readProjectConfiguration(tree, 'lib'); + + await convertToNxProject(tree, { project: 'lib' }); + const newConfigFile = await readJson( + tree, + getProjectConfigurationPath(config) + ); + + expect(config).toEqual(newConfigFile); + }); + + it('should extract all project configurations to project.json', async () => { + const tree = createTreeWithEmptyWorkspace(2); + + await libraryGenerator(tree, { + name: 'lib', + standaloneConfig: false, + }); + + await libraryGenerator(tree, { + name: 'lib2', + standaloneConfig: false, + }); + + const configs = ['lib', 'lib2'].map((x) => + readProjectConfiguration(tree, x) + ); + + await convertToNxProject(tree, { all: true }); + + for (const config of configs) { + const newConfigFile = await readJson( + tree, + getProjectConfigurationPath(config) + ); + expect(config).toEqual(newConfigFile); + } + }); + + it('should extract tags from nx.json into project.json', async () => { + const tree = createTreeWithEmptyWorkspace(2); + + await libraryGenerator(tree, { + name: 'lib', + tags: 'scope:test', + standaloneConfig: false, + }); + + const config = readProjectConfiguration(tree, 'lib'); + + await convertToNxProject(tree, { all: true }); + + const newConfigFile = await readJson( + tree, + getProjectConfigurationPath(config) + ); + expect(newConfigFile.tags).toEqual(['scope:test']); + }); + + it('should set workspace.json to point to the root directory', async () => { + const tree = createTreeWithEmptyWorkspace(2); + await libraryGenerator(tree, { + name: 'lib', + standaloneConfig: false, + }); + + const config = readProjectConfiguration(tree, 'lib'); + await convertToNxProject(tree, { project: 'lib' }); + const json = readJson(tree, 'workspace.json'); + expect(json.projects.lib).toEqual(config.root); + }); +}); diff --git a/packages/workspace/src/generators/convert-to-nx-project/convert-to-nx-project.ts b/packages/workspace/src/generators/convert-to-nx-project/convert-to-nx-project.ts new file mode 100644 index 0000000000..f3cb4706d8 --- /dev/null +++ b/packages/workspace/src/generators/convert-to-nx-project/convert-to-nx-project.ts @@ -0,0 +1,81 @@ +import { dirname } from 'path'; +import { prompt } from 'enquirer'; + +import { + convertNxGenerator, + formatFiles, + getProjects, + getWorkspacePath, + logger, + NxJsonConfiguration, + NxJsonProjectConfiguration, + ProjectConfiguration, + readProjectConfiguration, + Tree, + updateJson, + writeJson, +} from '@nrwl/devkit'; + +import { Schema } from './schema'; +import { checkIfNxProjectFileExists } from './utils/check-if-nx-project-file-exists'; +import { getProjectConfigurationPath } from './utils/get-project-configuration-path'; + +export const SCHEMA_OPTIONS_ARE_MUTUALLY_EXCLUSIVE = + '--project and --all are mutually exclusive'; + +export async function validateSchema(schema: Schema) { + if (schema.project && schema.all) { + throw SCHEMA_OPTIONS_ARE_MUTUALLY_EXCLUSIVE; + } + + if (!schema.project && !schema.all) { + schema.project = ( + await prompt<{ project: string }>([ + { + message: 'What project should be converted?', + type: 'input', + name: 'project', + }, + ]) + ).project; + } +} + +export async function convertToNxProjectGenerator(host: Tree, schema: Schema) { + await validateSchema(schema); + + const projects = schema.all + ? getProjects(host).entries() + : ([[schema.project, readProjectConfiguration(host, schema.project)]] as [ + string, + ProjectConfiguration & NxJsonProjectConfiguration + ][]); + + for (const [project, configuration] of projects) { + if (checkIfNxProjectFileExists(host, configuration)) { + logger.warn(`Skipping ${project} since ${configuration}`); + continue; + } + const configPath = getProjectConfigurationPath(configuration); + + writeJson(host, configPath, configuration); + + updateJson(host, getWorkspacePath(host), (value) => { + value.projects[project] = dirname(configPath); + return value; + }); + + updateJson(host, 'nx.json', (value: NxJsonConfiguration) => { + delete value.projects[project]; + return value; + }); + } + + await formatFiles(host); +} + +export default convertToNxProjectGenerator; + +export const convertToNxProjectSchematic = convertNxGenerator( + convertToNxProjectGenerator +); diff --git a/packages/workspace/src/generators/convert-to-nx-project/schema.d.ts b/packages/workspace/src/generators/convert-to-nx-project/schema.d.ts new file mode 100644 index 0000000000..51195983e7 --- /dev/null +++ b/packages/workspace/src/generators/convert-to-nx-project/schema.d.ts @@ -0,0 +1,4 @@ +export interface Schema { + project?: string; + all?: boolean; +} diff --git a/packages/workspace/src/generators/convert-to-nx-project/schema.json b/packages/workspace/src/generators/convert-to-nx-project/schema.json new file mode 100644 index 0000000000..98c1acd6b8 --- /dev/null +++ b/packages/workspace/src/generators/convert-to-nx-project/schema.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json-schema.org/schema", + "$id": "SchematicsConvertToNxProject", + "title": "Create a custom target to run any command", + "type": "object", + "cli": "nx", + "examples": [ + { + "command": "g @nrwl/workspace:convert-to-nx-project --project my-feature-lib", + "description": "Convert the my-feature-lib project to use project.json file instead of workspace.json" + }, + { + "command": "g @nrwl/workspace:convert-to-nx-project --all", + "description": "Convert all projects in workspace.json to separate project.json files." + } + ], + "properties": { + "project": { + "description": "Project name", + "type": "string", + "x-prompt": "Which project should be converted?" + }, + "all": { + "description": "Should every project be converted?", + "type": "boolean" + } + } +} diff --git a/packages/workspace/src/generators/convert-to-nx-project/utils/check-if-nx-project-file-exists.spec.ts b/packages/workspace/src/generators/convert-to-nx-project/utils/check-if-nx-project-file-exists.spec.ts new file mode 100644 index 0000000000..3433175ece --- /dev/null +++ b/packages/workspace/src/generators/convert-to-nx-project/utils/check-if-nx-project-file-exists.spec.ts @@ -0,0 +1,32 @@ +import { + addProjectConfiguration, + ProjectConfiguration, + Tree, +} from '@nrwl/devkit'; +import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; +import { join } from 'path'; +import { checkIfNxProjectFileExists } from './check-if-nx-project-file-exists'; + +describe('check if project.json file exists', () => { + let tree: Tree; + let projectConfig: ProjectConfiguration = { + root: 'apps/test-project', + targets: {}, + }; + + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(); + addProjectConfiguration(tree, 'test-project', projectConfig); + }); + + it('should return false if project.json does not exist', () => { + const result = checkIfNxProjectFileExists(tree, projectConfig); + expect(result).toBeFalsy(); + }); + + it('should return true if project.json does exist', () => { + tree.write(join(projectConfig.root, 'project.json'), ''); + const result = checkIfNxProjectFileExists(tree, projectConfig); + expect(result).toBeTruthy(); + }); +}); diff --git a/packages/workspace/src/generators/convert-to-nx-project/utils/check-if-nx-project-file-exists.ts b/packages/workspace/src/generators/convert-to-nx-project/utils/check-if-nx-project-file-exists.ts new file mode 100644 index 0000000000..a704e18cae --- /dev/null +++ b/packages/workspace/src/generators/convert-to-nx-project/utils/check-if-nx-project-file-exists.ts @@ -0,0 +1,9 @@ +import { ProjectConfiguration, Tree } from '@nrwl/devkit'; +import { getProjectConfigurationPath } from './get-project-configuration-path'; + +export function checkIfNxProjectFileExists( + host: Tree, + project: ProjectConfiguration +) { + return host.exists(getProjectConfigurationPath(project)); +} diff --git a/packages/workspace/src/generators/convert-to-nx-project/utils/get-project-configuration-path.ts b/packages/workspace/src/generators/convert-to-nx-project/utils/get-project-configuration-path.ts new file mode 100644 index 0000000000..ff9ac8e5e0 --- /dev/null +++ b/packages/workspace/src/generators/convert-to-nx-project/utils/get-project-configuration-path.ts @@ -0,0 +1,8 @@ +import { ProjectConfiguration } from '@nrwl/devkit'; +import { join } from 'path'; + +export function getProjectConfigurationPath( + configuration: ProjectConfiguration +) { + return join(configuration.root, 'project.json'); +} diff --git a/packages/workspace/src/generators/library/library.spec.ts b/packages/workspace/src/generators/library/library.spec.ts index 0e66f37fab..683689e040 100644 --- a/packages/workspace/src/generators/library/library.spec.ts +++ b/packages/workspace/src/generators/library/library.spec.ts @@ -17,6 +17,7 @@ describe('lib', () => { js: false, pascalCaseFiles: false, strict: false, + standaloneConfig: false, }; beforeEach(() => { diff --git a/packages/workspace/src/generators/library/library.ts b/packages/workspace/src/generators/library/library.ts index de07b2204f..8a1a86e286 100644 --- a/packages/workspace/src/generators/library/library.ts +++ b/packages/workspace/src/generators/library/library.ts @@ -56,7 +56,12 @@ function addProject(tree: Tree, options: NormalizedSchema) { }; } - addProjectConfiguration(tree, options.name, projectConfiguration); + addProjectConfiguration( + tree, + options.name, + projectConfiguration, + options.standaloneConfig + ); } export function addLint( diff --git a/packages/workspace/src/generators/library/schema.d.ts b/packages/workspace/src/generators/library/schema.d.ts index 04a8181310..c9dae74e97 100644 --- a/packages/workspace/src/generators/library/schema.d.ts +++ b/packages/workspace/src/generators/library/schema.d.ts @@ -19,4 +19,5 @@ export interface Schema { skipBabelrc?: boolean; buildable?: boolean; setParserOptionsProject?: boolean; + standaloneConfig?: boolean; } diff --git a/packages/workspace/src/generators/library/schema.json b/packages/workspace/src/generators/library/schema.json index d85024353e..2902f439ac 100644 --- a/packages/workspace/src/generators/library/schema.json +++ b/packages/workspace/src/generators/library/schema.json @@ -96,6 +96,11 @@ "type": "boolean", "description": "Whether or not to configure the ESLint \"parserOptions.project\" option. We do not do this by default for lint performance reasons.", "default": false + }, + "standaloneConfig": { + "description": "Split the project configuration into /project.json rather than including it inside workspace.json", + "type": "boolean", + "default": false } }, "required": ["name"] diff --git a/packages/workspace/src/generators/move/lib/check-destination.spec.ts b/packages/workspace/src/generators/move/lib/check-destination.spec.ts index 5128c9c3f9..71883c65e2 100644 --- a/packages/workspace/src/generators/move/lib/check-destination.spec.ts +++ b/packages/workspace/src/generators/move/lib/check-destination.spec.ts @@ -14,7 +14,7 @@ describe('checkDestination', () => { beforeEach(async () => { tree = createTreeWithEmptyWorkspace(); - await libraryGenerator(tree, { name: 'my-lib' }); + await libraryGenerator(tree, { name: 'my-lib', standaloneConfig: false }); projectConfig = readProjectConfiguration(tree, 'my-lib'); }); @@ -34,7 +34,10 @@ describe('checkDestination', () => { }); it('should throw an error if the path already exists', async () => { - await libraryGenerator(tree, { name: 'my-other-lib' }); + await libraryGenerator(tree, { + name: 'my-other-lib', + standaloneConfig: false, + }); const schema: Schema = { projectName: 'my-lib', diff --git a/packages/workspace/src/generators/move/lib/move-project.spec.ts b/packages/workspace/src/generators/move/lib/move-project.spec.ts index f71b88cbc2..9215a04a9d 100644 --- a/packages/workspace/src/generators/move/lib/move-project.spec.ts +++ b/packages/workspace/src/generators/move/lib/move-project.spec.ts @@ -14,7 +14,7 @@ describe('moveProject', () => { beforeEach(async () => { tree = createTreeWithEmptyWorkspace(); - await libraryGenerator(tree, { name: 'my-lib' }); + await libraryGenerator(tree, { name: 'my-lib', standaloneConfig: false }); projectConfig = readProjectConfiguration(tree, 'my-lib'); }); diff --git a/packages/workspace/src/generators/move/lib/update-cypress-json.spec.ts b/packages/workspace/src/generators/move/lib/update-cypress-json.spec.ts index 546ea42e15..b76ba60f7c 100644 --- a/packages/workspace/src/generators/move/lib/update-cypress-json.spec.ts +++ b/packages/workspace/src/generators/move/lib/update-cypress-json.spec.ts @@ -24,7 +24,7 @@ describe('updateCypressJson', () => { }; tree = createTreeWithEmptyWorkspace(); - await libraryGenerator(tree, { name: 'my-lib' }); + await libraryGenerator(tree, { name: 'my-lib', standaloneConfig: false }); projectConfig = readProjectConfiguration(tree, 'my-lib'); }); diff --git a/packages/workspace/src/generators/move/lib/update-eslintrc-json.spec.ts b/packages/workspace/src/generators/move/lib/update-eslintrc-json.spec.ts index 3b8fa03485..b3efbd82de 100644 --- a/packages/workspace/src/generators/move/lib/update-eslintrc-json.spec.ts +++ b/packages/workspace/src/generators/move/lib/update-eslintrc-json.spec.ts @@ -27,6 +27,7 @@ describe('updateEslint', () => { await libraryGenerator(tree, { name: 'my-lib', linter: Linter.TsLint, + standaloneConfig: false, }); const projectConfig = readProjectConfiguration(tree, 'my-lib'); @@ -40,6 +41,7 @@ describe('updateEslint', () => { await libraryGenerator(tree, { name: 'my-lib', linter: Linter.EsLint, + standaloneConfig: false, }); // This step is usually handled elsewhere @@ -66,6 +68,7 @@ describe('updateEslint', () => { name: 'my-lib', linter: Linter.EsLint, setParserOptionsProject: true, + standaloneConfig: false, }); // This step is usually handled elsewhere diff --git a/packages/workspace/src/generators/move/lib/update-imports.spec.ts b/packages/workspace/src/generators/move/lib/update-imports.spec.ts index e378c67931..2ab88de70e 100644 --- a/packages/workspace/src/generators/move/lib/update-imports.spec.ts +++ b/packages/workspace/src/generators/move/lib/update-imports.spec.ts @@ -23,10 +23,19 @@ describe('updateImports', () => { // this is a bit of a cheat - we expect to run this rule on an intermediate state // tree where the workspace hasn't been updated yet, so just create libs representing // source and destination to make sure that the workspace has libraries with those names. - await libraryGenerator(tree, { name: 'my-destination' }); - await libraryGenerator(tree, { name: 'my-source' }); + await libraryGenerator(tree, { + name: 'my-destination', + standaloneConfig: false, + }); + await libraryGenerator(tree, { + name: 'my-source', + standaloneConfig: false, + }); - await libraryGenerator(tree, { name: 'my-importer' }); + await libraryGenerator(tree, { + name: 'my-importer', + standaloneConfig: false, + }); const importerFilePath = 'libs/my-importer/src/importer.ts'; tree.write( importerFilePath, @@ -49,10 +58,13 @@ describe('updateImports', () => { * be updated. */ it('should not update import paths when they contain a partial match', async () => { - await libraryGenerator(tree, { name: 'table' }); - await libraryGenerator(tree, { name: 'tab' }); + await libraryGenerator(tree, { name: 'table', standaloneConfig: false }); + await libraryGenerator(tree, { name: 'tab', standaloneConfig: false }); - await libraryGenerator(tree, { name: 'my-importer' }); + await libraryGenerator(tree, { + name: 'my-importer', + standaloneConfig: false, + }); const importerFilePath = 'libs/my-importer/src/importer.ts'; tree.write( importerFilePath, @@ -89,10 +101,13 @@ describe('updateImports', () => { }); it('should correctly update deep imports', async () => { - await libraryGenerator(tree, { name: 'table' }); - await libraryGenerator(tree, { name: 'tab' }); + await libraryGenerator(tree, { name: 'table', standaloneConfig: false }); + await libraryGenerator(tree, { name: 'tab', standaloneConfig: false }); - await libraryGenerator(tree, { name: 'my-importer' }); + await libraryGenerator(tree, { + name: 'my-importer', + standaloneConfig: false, + }); const importerFilePath = 'libs/my-importer/src/importer.ts'; tree.write( importerFilePath, @@ -129,10 +144,13 @@ describe('updateImports', () => { }); it('should update dynamic imports', async () => { - await libraryGenerator(tree, { name: 'table' }); - await libraryGenerator(tree, { name: 'tab' }); + await libraryGenerator(tree, { name: 'table', standaloneConfig: false }); + await libraryGenerator(tree, { name: 'tab', standaloneConfig: false }); - await libraryGenerator(tree, { name: 'my-importer' }); + await libraryGenerator(tree, { + name: 'my-importer', + standaloneConfig: false, + }); const importerFilePath = 'libs/my-importer/src/importer.ts'; tree.write( importerFilePath, diff --git a/packages/workspace/src/generators/move/lib/update-jest-config.spec.ts b/packages/workspace/src/generators/move/lib/update-jest-config.spec.ts index d9c2ccacfe..48fbf1c02a 100644 --- a/packages/workspace/src/generators/move/lib/update-jest-config.spec.ts +++ b/packages/workspace/src/generators/move/lib/update-jest-config.spec.ts @@ -15,6 +15,7 @@ describe('updateJestConfig', () => { it('should handle jest config not existing', async () => { await libraryGenerator(tree, { name: 'my-source', + standaloneConfig: false, }); const projectConfig = readProjectConfiguration(tree, 'my-source'); @@ -44,6 +45,7 @@ describe('updateJestConfig', () => { await libraryGenerator(tree, { name: 'my-source', + standaloneConfig: false, }); const projectConfig = readProjectConfiguration(tree, 'my-source'); tree.write(jestConfigPath, jestConfig); diff --git a/packages/workspace/src/generators/move/lib/update-package-json.spec.ts b/packages/workspace/src/generators/move/lib/update-package-json.spec.ts index fc8df65179..acceb8c3aa 100644 --- a/packages/workspace/src/generators/move/lib/update-package-json.spec.ts +++ b/packages/workspace/src/generators/move/lib/update-package-json.spec.ts @@ -24,7 +24,7 @@ describe('updatePackageJson', () => { }; tree = createTreeWithEmptyWorkspace(); - await libraryGenerator(tree, { name: 'my-lib' }); + await libraryGenerator(tree, { name: 'my-lib', standaloneConfig: false }); projectConfig = readProjectConfiguration(tree, 'my-lib'); }); diff --git a/packages/workspace/src/generators/move/lib/update-project-root-files.spec.ts b/packages/workspace/src/generators/move/lib/update-project-root-files.spec.ts index e99a95504d..d8802e14e4 100644 --- a/packages/workspace/src/generators/move/lib/update-project-root-files.spec.ts +++ b/packages/workspace/src/generators/move/lib/update-project-root-files.spec.ts @@ -25,6 +25,7 @@ describe('updateProjectRootFiles', () => { await libraryGenerator(tree, { name: 'my-source', + standaloneConfig: false, }); const projectConfig = readProjectConfiguration(tree, 'my-source'); tree.write(testFilePath, testFile); diff --git a/packages/workspace/src/generators/move/lib/update-readme.spec.ts b/packages/workspace/src/generators/move/lib/update-readme.spec.ts index c4861a5fb1..343a6c4476 100644 --- a/packages/workspace/src/generators/move/lib/update-readme.spec.ts +++ b/packages/workspace/src/generators/move/lib/update-readme.spec.ts @@ -25,6 +25,7 @@ describe('updateReadme', () => { it('should handle README.md not existing', async () => { await libraryGenerator(tree, { name: 'my-lib', + standaloneConfig: false, }); const projectConfig = readProjectConfiguration(tree, 'my-lib'); @@ -40,6 +41,7 @@ describe('updateReadme', () => { it('should update README.md contents', async () => { await libraryGenerator(tree, { name: 'my-lib', + standaloneConfig: false, }); // This step is usually handled elsewhere diff --git a/packages/workspace/src/generators/move/lib/update-storybook-config.spec.ts b/packages/workspace/src/generators/move/lib/update-storybook-config.spec.ts index 97682e8132..35f801f443 100644 --- a/packages/workspace/src/generators/move/lib/update-storybook-config.spec.ts +++ b/packages/workspace/src/generators/move/lib/update-storybook-config.spec.ts @@ -14,6 +14,7 @@ describe('updateStorybookConfig', () => { it('should handle storybook config not existing', async () => { await libraryGenerator(tree, { name: 'my-source', + standaloneConfig: false, }); const projectConfig = readProjectConfiguration(tree, 'my-source'); @@ -38,6 +39,7 @@ describe('updateStorybookConfig', () => { await libraryGenerator(tree, { name: 'my-source', + standaloneConfig: false, }); const projectConfig = readProjectConfiguration(tree, 'my-source'); tree.write(storybookMainPath, storybookMain); @@ -67,6 +69,7 @@ describe('updateStorybookConfig', () => { await libraryGenerator(tree, { name: 'my-source', + standaloneConfig: false, }); const projectConfig = readProjectConfiguration(tree, 'my-source'); tree.write(storybookWebpackConfigPath, storybookWebpackConfig); diff --git a/packages/workspace/src/generators/preset/preset.spec.ts b/packages/workspace/src/generators/preset/preset.spec.ts index 85ebcb80a0..41e3b1999c 100644 --- a/packages/workspace/src/generators/preset/preset.spec.ts +++ b/packages/workspace/src/generators/preset/preset.spec.ts @@ -47,6 +47,7 @@ describe('preset', () => { cli: 'nx', style: 'css', linter: 'eslint', + standaloneConfig: false, }); expect(tree.children('apps/proj')).toMatchSnapshot(); expect(tree.children('apps/proj/src/')).toMatchSnapshot(); @@ -62,6 +63,7 @@ describe('preset', () => { name: 'proj', preset: 'web-components', cli: 'nx', + standaloneConfig: false, }); expect(tree.exists('/apps/proj/src/main.ts')).toBe(true); expect(readJson(tree, '/workspace.json').cli.defaultCollection).toBe( @@ -76,6 +78,7 @@ describe('preset', () => { style: 'css', linter: 'eslint', cli: 'nx', + standaloneConfig: false, }); expect(tree.exists('/apps/proj/src/main.tsx')).toBe(true); expect(readJson(tree, '/workspace.json').cli.defaultCollection).toBe( @@ -90,6 +93,7 @@ describe('preset', () => { style: 'css', linter: 'eslint', cli: 'nx', + standaloneConfig: false, }); expect(tree.exists('/apps/proj/pages/index.tsx')).toBe(true); expect(readJson(tree, '/workspace.json').cli.defaultCollection).toBe( @@ -104,6 +108,7 @@ describe('preset', () => { style: 'css', linter: 'eslint', cli: 'nx', + standaloneConfig: false, }); expect(tree.exists('/apps/proj/src/app/app.component.ts')).toBe(true); @@ -120,6 +125,7 @@ describe('preset', () => { style: 'css', linter: 'eslint', cli: 'nx', + standaloneConfig: false, }); expect(tree.exists('/apps/proj/src/app/app.tsx')).toBe(true); @@ -137,6 +143,7 @@ describe('preset', () => { preset: 'express', linter: 'eslint', cli: 'nx', + standaloneConfig: false, }); expect(tree.exists('apps/proj/src/main.ts')).toBe(true); @@ -150,6 +157,7 @@ describe('preset', () => { style: 'css', linter: 'eslint', cli: 'nx', + standaloneConfig: false, }); expect(tree.exists('/apps/proj/src/pages/index.tsx')).toBe(true); diff --git a/packages/workspace/src/generators/preset/preset.ts b/packages/workspace/src/generators/preset/preset.ts index 3e2659f443..2df266b334 100644 --- a/packages/workspace/src/generators/preset/preset.ts +++ b/packages/workspace/src/generators/preset/preset.ts @@ -41,6 +41,7 @@ async function createPreset(tree: Tree, options: Schema) { name: options.name, style: options.style, linter: options.linter, + standaloneConfig: options.standaloneConfig, }); setDefaultCollection(tree, '@nrwl/angular'); } else if (options.preset === 'react') { @@ -52,6 +53,7 @@ async function createPreset(tree: Tree, options: Schema) { name: options.name, style: options.style, linter: options.linter, + standaloneConfig: options.standaloneConfig, }); setDefaultCollection(tree, '@nrwl/react'); } else if (options.preset === 'next') { @@ -62,6 +64,7 @@ async function createPreset(tree: Tree, options: Schema) { name: options.name, style: options.style, linter: options.linter, + standaloneConfig: options.standaloneConfig, }); setDefaultCollection(tree, '@nrwl/next'); } else if (options.preset === 'web-components') { @@ -72,6 +75,7 @@ async function createPreset(tree: Tree, options: Schema) { name: options.name, style: options.style, linter: options.linter, + standaloneConfig: options.standaloneConfig, }); addDependenciesToPackageJson( tree, @@ -98,16 +102,19 @@ async function createPreset(tree: Tree, options: Schema) { style: options.style, linter: options.linter, skipFormat: true, + standaloneConfig: options.standaloneConfig, }); await nestApplicationGenerator(tree, { name: 'api', frontendProject: options.name, linter: options.linter, + standaloneConfig: options.standaloneConfig, }); await libraryGenerator(tree, { name: 'api-interfaces', unitTestRunner: 'none', linter: options.linter, + standaloneConfig: options.standaloneConfig, }); setDefaultCollection(tree, '@nrwl/angular'); connectAngularAndNest(tree, options); @@ -123,16 +130,19 @@ async function createPreset(tree: Tree, options: Schema) { name: options.name, style: options.style, linter: options.linter, + standaloneConfig: options.standaloneConfig, }); await expressApplicationGenerator(tree, { name: 'api', frontendProject: options.name, linter: options.linter, + standaloneConfig: options.standaloneConfig, }); await libraryGenerator(tree, { name: 'api-interfaces', unitTestRunner: 'none', linter: options.linter, + standaloneConfig: options.standaloneConfig, }); setDefaultCollection(tree, '@nrwl/react'); connectReactAndExpress(tree, options); @@ -152,6 +162,7 @@ async function createPreset(tree: Tree, options: Schema) { await expressApplicationGenerator(tree, { name: options.name, linter: options.linter, + standaloneConfig: options.standaloneConfig, }); setDefaultCollection(tree, '@nrwl/express'); } else if (options.preset === 'gatsby') { @@ -162,6 +173,7 @@ async function createPreset(tree: Tree, options: Schema) { name: options.name, linter: options.linter, style: options.style, + standaloneConfig: options.standaloneConfig, }); setDefaultCollection(tree, '@nrwl/gatsby'); } else { diff --git a/packages/workspace/src/generators/preset/schema.d.ts b/packages/workspace/src/generators/preset/schema.d.ts index 692ac7e245..eee31e03bb 100644 --- a/packages/workspace/src/generators/preset/schema.d.ts +++ b/packages/workspace/src/generators/preset/schema.d.ts @@ -16,4 +16,5 @@ export interface Schema { | 'react-express' | 'nest' | 'express'; + standaloneConfig?: boolean; } diff --git a/packages/workspace/src/generators/preset/schema.json b/packages/workspace/src/generators/preset/schema.json index c66a70fe43..223aaa4634 100644 --- a/packages/workspace/src/generators/preset/schema.json +++ b/packages/workspace/src/generators/preset/schema.json @@ -50,6 +50,11 @@ } ] } + }, + "standaloneConfig": { + "description": "Split the project configurations into /project.json rather than including it inside workspace.json", + "type": "boolean", + "default": false } } } diff --git a/packages/workspace/src/generators/remove/lib/check-dependencies.spec.ts b/packages/workspace/src/generators/remove/lib/check-dependencies.spec.ts index ba1b09265b..a78dea31fc 100644 --- a/packages/workspace/src/generators/remove/lib/check-dependencies.spec.ts +++ b/packages/workspace/src/generators/remove/lib/check-dependencies.spec.ts @@ -29,9 +29,11 @@ describe('checkDependencies', () => { await libraryGenerator(tree, { name: 'my-dependent', + standaloneConfig: false, }); await libraryGenerator(tree, { name: 'my-source', + standaloneConfig: false, }); projectGraph = { diff --git a/packages/workspace/src/generators/remove/lib/remove-project.spec.ts b/packages/workspace/src/generators/remove/lib/remove-project.spec.ts index c6dd60301c..edfcd3d491 100644 --- a/packages/workspace/src/generators/remove/lib/remove-project.spec.ts +++ b/packages/workspace/src/generators/remove/lib/remove-project.spec.ts @@ -12,6 +12,7 @@ describe('moveProject', () => { tree = createTreeWithEmptyWorkspace(); await libraryGenerator(tree, { name: 'my-lib', + standaloneConfig: false, }); schema = { @@ -22,7 +23,8 @@ describe('moveProject', () => { }); it('should delete the project folder', async () => { - removeProject(tree, readProjectConfiguration(tree, 'my-lib')); + const config = readProjectConfiguration(tree, 'my-lib'); + removeProject(tree, config); expect(tree.children('libs')).not.toContain('my-lib'); }); }); diff --git a/packages/workspace/src/generators/remove/lib/update-jest-config.spec.ts b/packages/workspace/src/generators/remove/lib/update-jest-config.spec.ts index 0beb5a702f..18484127b0 100644 --- a/packages/workspace/src/generators/remove/lib/update-jest-config.spec.ts +++ b/packages/workspace/src/generators/remove/lib/update-jest-config.spec.ts @@ -23,9 +23,11 @@ describe('updateRootJestConfig', () => { await libraryGenerator(tree, { name: 'my-lib', + standaloneConfig: false, }); await libraryGenerator(tree, { name: 'my-other-lib', + standaloneConfig: false, }); tree.write( diff --git a/packages/workspace/src/generators/remove/lib/update-tsconfig.spec.ts b/packages/workspace/src/generators/remove/lib/update-tsconfig.spec.ts index 3c4baa3ed9..31a0268377 100644 --- a/packages/workspace/src/generators/remove/lib/update-tsconfig.spec.ts +++ b/packages/workspace/src/generators/remove/lib/update-tsconfig.spec.ts @@ -21,6 +21,7 @@ describe('updateTsconfig', () => { it('should delete project ref from the tsconfig', async () => { await libraryGenerator(tree, { name: 'my-lib', + standaloneConfig: false, }); const project = readProjectConfiguration(tree, 'my-lib'); diff --git a/packages/workspace/src/generators/run-commands/run-commands.spec.ts b/packages/workspace/src/generators/run-commands/run-commands.spec.ts index 2d9afdadae..adcb3e1355 100644 --- a/packages/workspace/src/generators/run-commands/run-commands.spec.ts +++ b/packages/workspace/src/generators/run-commands/run-commands.spec.ts @@ -15,6 +15,7 @@ describe('run-commands', () => { await libraryGenerator(tree, { name: 'lib', + standaloneConfig: false, }); await runCommands(tree, opts); diff --git a/packages/workspace/src/utils/ast-utils.ts b/packages/workspace/src/utils/ast-utils.ts index 9414ffc2fe..aeb05d7d49 100644 --- a/packages/workspace/src/utils/ast-utils.ts +++ b/packages/workspace/src/utils/ast-utils.ts @@ -594,8 +594,11 @@ export function updatePackageJsonDependencies( export function getProjectConfig(host: Tree, name: string): any { const workspaceJson = readJsonInTree(host, getWorkspacePath(host)); const projectConfig = workspaceJson.projects[name]; + if (!projectConfig) { throw new Error(`Cannot find project '${name}'`); + } else if (typeof projectConfig === 'string') { + return readJsonInTree(host, projectConfig); } else { return projectConfig; } diff --git a/packages/workspace/src/utils/rules/format-files.spec.ts b/packages/workspace/src/utils/rules/format-files.spec.ts index 0d13d6850a..47dc7327bf 100644 --- a/packages/workspace/src/utils/rules/format-files.spec.ts +++ b/packages/workspace/src/utils/rules/format-files.spec.ts @@ -34,7 +34,7 @@ describe('formatFiles', () => { .toPromise(); expect(prettier.format).toHaveBeenCalledWith('const a=a', { printWidth: 80, - filepath: `${appRootPath}/a.ts`, + filepath: path.join(appRootPath, 'a.ts'), }); expect(result.read('a.ts').toString()).toEqual('formatted :: const a=a'); }); @@ -64,7 +64,7 @@ describe('formatFiles', () => { .callRule(formatFiles(), tree) .toPromise(); expect(prettier.format).toHaveBeenCalledWith('const a=b', { - filepath: `${appRootPath}/a.ts`, + filepath: path.join(appRootPath, 'a.ts'), }); expect(result.read('a.ts').toString()).toEqual('formatted :: const a=b'); }); @@ -79,7 +79,7 @@ describe('formatFiles', () => { .callRule(formatFiles(), tree) .toPromise(); expect(prettier.format).toHaveBeenCalledWith('const a=a', { - filepath: `${appRootPath}/b.ts`, + filepath: path.join(appRootPath, 'b.ts'), }); expect(result.read('b.ts').toString()).toEqual('formatted :: const a=a'); });