Skip to content

Commit

Permalink
feat(core): support for workspace solution files (#254)
Browse files Browse the repository at this point in the history
  • Loading branch information
AgentEnder authored Nov 29, 2021
1 parent dbff13c commit ec342ae
Show file tree
Hide file tree
Showing 62 changed files with 580 additions and 273 deletions.
1 change: 0 additions & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{
"recommendations": [
"ms-vscode.vscode-typescript-tslint-plugin",
"esbenp.prettier-vscode",
"firsttris.vscode-jest-runner",
"mike-co.import-sorter"
Expand Down
3 changes: 3 additions & 0 deletions apps/docs-site/docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ module.exports = {
contextualSearch: false,
appId: 'BH4D9OD16A',
},
prism: {
additionalLanguages: ['json5', 'typescript', 'bash'],
},
},
presets: [
[
Expand Down
6 changes: 6 additions & 0 deletions apps/docs-site/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@
"outputPath": "dist/apps/docs-site"
}
},
"prebuild": {
"executor": "@nrwl/workspace:run-commands",
"options": {
"commands": ["echo"]
}
},
"serve": {
"executor": "@nx-plus/docusaurus:dev-server",
"options": {
Expand Down
6 changes: 6 additions & 0 deletions docs/core/generators/application.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,9 @@ Generate a dotnet project under the application directory.
### standalone

- (boolean): Should the project use project.json? If false, the project config is inside workspace.json

### solutionFile

- (string): The name of the solution file to add the project to

- (boolean): Should the project be added to the default solution file?
6 changes: 6 additions & 0 deletions docs/core/generators/library.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,9 @@ Generate a dotnet project under the library directory.
### standalone

- (boolean): Should the project use project.json? If false, the project config is inside workspace.json

### solutionFile

- (string): The name of the solution file to add the project to

- (boolean): Should the project be added to the default solution file?
6 changes: 6 additions & 0 deletions docs/core/generators/test.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,9 @@ Generate a .NET test project for an existing application or library
### standalone

- (boolean): Should the project use project.json? If false, the project config is inside workspace.json

### solutionFile

- (string): The name of the solution file to add the project to

- (boolean): Should the project be added to the default solution file?
2 changes: 2 additions & 0 deletions docs/core/guides/_category_.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
label: 'Guides'
position: 1
37 changes: 37 additions & 0 deletions docs/core/guides/handling-solutions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Handling Solution Files

## Workspace Level

As of v1.7.0, `nx-dotnet` supports adding projects to a workspace level solution file automatically. When generating an app, lib, or test project you can pass `--solutionFile` to add the project to the default solution at the workspace root. Alternatively, you can pass `--solutionFile {path/to/sln}` to add the project to a custom solution file. This should look something like:

```bash
npx nx g @nx-dotnet/core:app my-api --template webapi --solutionFile MyCompany.sln
```

To add projects to a solution file by default, you can set the generator defaults in [nx.json](https://nx.dev/l/a/core-concepts/configuration#nxjson) as below:

```json5
{
// ... more nx.json configuration
generators: {
// ... other default configurations
'@nx-dotnet/core:app': {
solutionFile: true,
},
},
}
```

## Subgraph Solutions

In a large monorepo, IDEs or other tooling may slow down when presented with a large solution file. Currently, `nx-dotnet` does not assist in managing this issue, but there are a few easy steps to take that can help optimize your workflow. Which path you take will depend on both the tooling you use, and the pains that you are enountering.

Either of the two approaches listed below could be expanded on in the future, but currently are not in the scope of the nx-plugin.

### Separate solution files

One option would be totally separated solution files for project graphs that are not connected. The main thing to be cautious with in an approach like this, is that if the dependency graph changes and the two subgraphs become connected it would be possible to make changes that break a project not currently visible to the IDE. For example, lets say you have 3 projects `A`, `B`, and `Shared`. If `A` and `B` both depend on `Shared`, and you have separate solutions each containing either `A` or `B` alongside the `Shared` project, a developer could modify the code in `Shared` and break the project that was not included in the opened solution file. As such, any solution that contained `Shared` _must_ contain all projects that depend on it to maintain good DX.

### Solution filters

Some IDEs such as Visual Studio support solution filters. These filters would allow for all projects to be visible to the IDE, but can have some performance benefits. The caveats to using separate files can still exist though, but these could be easier to maintain in the long run. Here is a link to the [msdn docs for solution filters](https://docs.microsoft.com/en-us/visualstudio/ide/filtered-solutions?view=vs-2022).
2 changes: 1 addition & 1 deletion e2e/core-e2e/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"e2e": {
"executor": "@nrwl/nx-plugin:e2e",
"options": {
"target": "core:build",
"target": "core:noop",
"jestConfig": "e2e/core-e2e/jest.config.js"
}
}
Expand Down
69 changes: 62 additions & 7 deletions e2e/core-e2e/tests/nx-dotnet.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import {
joinPathFragments,
names,
WorkspaceJsonConfiguration,
} from '@nrwl/devkit';
import { joinPathFragments, names } from '@nrwl/devkit';
import {
checkFilesExist,
ensureNxProject,
listFiles,
readFile,
readJson,
runNxCommandAsync,
uniq,
} from '@nrwl/nx-plugin/testing';
Expand All @@ -20,6 +16,7 @@ import { findProjectFileInPathSync } from '@nx-dotnet/utils';
import { readDependenciesFromNxDepGraph } from '@nx-dotnet/utils/e2e';
import { execSync } from 'child_process';
import { ensureDirSync } from 'fs-extra';
import { Workspaces } from '@nrwl/tao/src/shared/workspace';

const e2eDir = 'tmp/nx-e2e/proj';

Expand Down Expand Up @@ -234,7 +231,7 @@ describe('nx-dotnet e2e', () => {

await runNxCommandAsync(`generate @nx-dotnet/core:import-projects`);

const workspace = readJson<WorkspaceJsonConfiguration>('workspace.json');
const workspace = new Workspaces(e2eDir).readWorkspaceConfiguration();

expect(workspace.projects[testApp].targets?.serve).toBeDefined();
expect(workspace.projects[testApp].targets?.build).toBeDefined();
Expand All @@ -250,4 +247,62 @@ describe('nx-dotnet e2e', () => {
checkFilesExist(`dist/apps/${testApp}`);
});
});

describe('solution handling', () => {
// For solution handling, defaults fall back to if a file exists.
// This ensures that the tests are ran in a clean state, without previous
// test projects interfering with the test.
beforeEach(() => {
ensureNxProject('@nx-dotnet/core', 'dist/packages/core');
}, 1500000);

it("shouldn't create a solution by default if not specified", async () => {
const app = uniq('app');
await runNxCommandAsync(
`generate @nx-dotnet/core:app ${app} --language="C#" --template="webapi"`,
);

expect(() => checkFilesExist(`apps/${app}`)).not.toThrow();
expect(listFiles('.').filter((x) => x.endsWith('.sln'))).toHaveLength(0);
});

it('should create a default solution file if specified as true', async () => {
const app = uniq('app');
await runNxCommandAsync(
`generate @nx-dotnet/core:app ${app} --language="C#" --template="webapi" --solutionFile`,
);

expect(() => checkFilesExist(`apps/${app}`)).not.toThrow();
expect(listFiles('.').filter((x) => x.endsWith('.sln'))).toHaveLength(1);
});

it('should create specified solution file if specified as string', async () => {
const app = uniq('app');
await runNxCommandAsync(
`generate @nx-dotnet/core:app ${app} --language="C#" --template="webapi" --solutionFile="MyCompany.sln"`,
);

expect(() =>
checkFilesExist(`apps/${app}`, `MyCompany.sln`),
).not.toThrow();
});

it('should add successive projects to default solution file', async () => {
const app1 = uniq('app');
await runNxCommandAsync(
`generate @nx-dotnet/core:app ${app1} --language="C#" --template="webapi" --solutionFile`,
);

const app2 = uniq('app2');
await runNxCommandAsync(
`generate @nx-dotnet/core:app ${app2} --language="C#" --template="webapi" --solutionFile`,
);

const slnFile = readFile('proj.nx-dotnet.sln');

expect(() => checkFilesExist(`apps/${app1}`)).not.toThrow();
expect(slnFile).toContain(app1);
expect(slnFile).toContain(app2);
});
});
});
2 changes: 1 addition & 1 deletion e2e/nx-ghpages-e2e/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"e2e": {
"executor": "@nrwl/nx-plugin:e2e",
"options": {
"target": "nx-ghpages:build",
"target": "nx-ghpages:noop",
"jestConfig": "e2e/nx-ghpages-e2e/jest.config.js"
}
}
Expand Down
2 changes: 1 addition & 1 deletion e2e/nxdoc-e2e/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"e2e": {
"executor": "@nrwl/nx-plugin:e2e",
"options": {
"target": "nxdoc:build",
"target": "nxdoc:noop",
"jestConfig": "e2e/nxdoc-e2e/jest.config.js"
}
}
Expand Down
2 changes: 1 addition & 1 deletion e2e/typescript-e2e/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"e2e": {
"executor": "@nrwl/nx-plugin:e2e",
"options": {
"target": "typescript:build",
"target": "typescript:noop",
"jestConfig": "e2e/typescript-e2e/jest.config.js"
}
}
Expand Down
4 changes: 4 additions & 0 deletions nx.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@
{
"target": "build",
"projects": "dependencies"
},
{
"target": "prebuild",
"projects": "self"
}
]
},
Expand Down
28 changes: 4 additions & 24 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,17 @@
"name": "nx-dotnet",
"license": "MIT",
"scripts": {
"nx": "nx",
"start": "nx serve",
"build": "nx build",
"test": "nx test",
"lint": "nx workspace-lint && nx lint",
"prepare": "husky install",
"lint-staged": "lint-staged",
"affected:apps": "nx affected:apps",
"affected:libs": "nx affected:libs",
"affected:build": "nx affected:build",
"affected:e2e": "nx affected:e2e",
"affected:test": "nx affected:test",
"affected:lint": "nx affected:lint",
"affected:dep-graph": "nx affected:dep-graph",
"affected": "nx affected",
"format": "nx format:write",
"format:write": "nx format:write",
"format:check": "nx format:check",
"update": "nx migrate latest",
"workspace-generator": "nx workspace-generator",
"dep-graph": "nx dep-graph",
"help": "nx help",
"publish-dev": "ts-node tools/scripts/publish-dev",
"e2e-registry": "yarn verdaccio --config ./tools/scripts/local-registry/config.yml --listen 4872",
"e2e-tests": "ts-node -P ./tools/scripts/tsconfig.e2e.json ./tools/scripts/e2e.ts",
"e2e": "run-p -r e2e-registry \"e2e-tests {@}\" --",
"e2e": "ts-node -P ./tools/scripts/tsconfig.e2e.json ./tools/scripts/e2e.ts",
"publish-local": "cp .npmrc.local .npmrc && run-p \"rimraf tmp\" e2e-registry \"ts-node ./tools/scripts/publish-all 99.99.99 local\"",
"semantic-release": "semantic-release",
"ts-node": "ts-node",
"rimraf": "rimraf",
"preinstall": "node ./tools/scripts/hooks/preinstall.js",
"documentation:check": "ts-node ./tools/scripts/hooks/documentation.check.ts"
"documentation:check": "ts-node ./tools/scripts/hooks/documentation.check.ts",
"sandbox": "ts-node ./tools/scripts/sandbox.ts"
},
"private": false,
"dependencies": {
Expand Down Expand Up @@ -93,6 +72,7 @@
"fs-extra": "^10.0.0",
"husky": "^7.0.2",
"jest": "27.3.1",
"kill-port": "^1.6.1",
"lint-staged": "^11.2.3",
"prettier": "2.4.1",
"run-p": "*",
Expand Down
4 changes: 2 additions & 2 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
"email": "craigorycoppola+nxdotnet@gmail.com"
},
"license": "MIT",
"version": "1.6.0",
"keywords": [
"Nx",
".NET",
Expand All @@ -32,5 +31,6 @@
},
"nx-migrations": {
"migrations": "./migrations.json"
}
},
"version": "1.6.0"
}
12 changes: 12 additions & 0 deletions packages/core/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@
"passWithNoTests": true
}
},
"prebuild": {
"executor": "@nrwl/workspace:run-commands",
"options": {
"commands": ["npx ts-node tools/scripts/patch-package-versions"]
}
},
"build": {
"executor": "@nrwl/node:package",
"outputs": ["{options.outputPath}"],
Expand Down Expand Up @@ -59,6 +65,12 @@
}
]
}
},
"noop": {
"executor": "@nrwl/workspace:run-commands",
"options": {
"command": "echo"
}
}
},
"tags": []
Expand Down
14 changes: 2 additions & 12 deletions packages/core/src/executors/build/executor.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import { ExecutorContext } from '@nrwl/devkit';
import { appRootPath } from '@nrwl/tao/src/utils/app-root';

import {
dotnetBuildFlags,
DotNetClient,
dotnetFactory,
} from '@nx-dotnet/dotnet';
import { DotNetClient, dotnetFactory } from '@nx-dotnet/dotnet';
import {
getExecutedProjectConfiguration,
getProjectFileForNxProject,
Expand All @@ -30,13 +26,7 @@ export default async function runExecutor(
? resolve(appRootPath, options.output)
: undefined;

dotnetClient.build(
projectFilePath,
Object.keys(options).map((x) => ({
flag: x as dotnetBuildFlags,
value: (options as Record<string, string | boolean>)[x],
})),
);
dotnetClient.build(projectFilePath, options);

return {
success: true,
Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/executors/format/executor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,8 @@ describe('Format Executor', () => {

const formatOptions = (dotnetClient as jest.Mocked<DotNetClient>).format
.mock.calls[0][1];
const checkFlag = formatOptions?.find((o) => o.flag == 'check');
expect(checkFlag?.value).toBeTruthy();
const checkFlag = formatOptions?.check;
expect(checkFlag).toBeTruthy();
});

it('passes the --verify-no-changes option on .NET 6 and later', async () => {
Expand All @@ -203,7 +203,7 @@ describe('Format Executor', () => {

const formatOptions = (dotnetClient as jest.Mocked<DotNetClient>).format
.mock.calls[0][1];
const checkFlag = formatOptions?.find((o) => o.flag == 'verifyNoChanges');
expect(checkFlag?.value).toBeTruthy();
const verifyNoChangesFlag = formatOptions?.verifyNoChanges;
expect(verifyNoChangesFlag).toBeTruthy();
});
});
Loading

0 comments on commit ec342ae

Please sign in to comment.