-
Notifications
You must be signed in to change notification settings - Fork 64
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(core): ability to import projects into the nx configuration
- Loading branch information
1 parent
0c3a3aa
commit 8be8446
Showing
12 changed files
with
302 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# @nx-dotnet/core:import-projects | ||
|
||
## Import Projects | ||
|
||
Import existing .NET projects in C#, VB, or F# that are in your workspace's apps or libs directories. Simply move the projects into these folders, and then run `nx g @nx-dotnet/core:import-projects` to move them into Nx. Projects inside the apps directory will include a serve target, while projects inside libs will only contain build + lint targets. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
106 changes: 106 additions & 0 deletions
106
packages/core/src/generators/import-projects/generator.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; | ||
import { Tree, readProjectConfiguration, getProjects } from '@nrwl/devkit'; | ||
|
||
import generator from './generator'; | ||
import * as utils from '@nx-dotnet/utils'; | ||
import * as fs from 'fs'; | ||
|
||
jest.mock('@nx-dotnet/utils', () => ({ | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
...(jest.requireActual('@nx-dotnet/utils') as any), | ||
glob: jest.fn(), | ||
findProjectFileInPath: jest.fn(), | ||
resolve: (m: string) => m, | ||
})); | ||
|
||
const MOCK_API_PROJECT = ` | ||
<Project Sdk="Microsoft.NET.Sdk.Web"> | ||
<PropertyGroup> | ||
<TargetFramework>net5.0</TargetFramework> | ||
<RootNamespace>MyTestApi</RootNamespace> | ||
</PropertyGroup> | ||
</Project>`; | ||
|
||
const MOCK_TEST_PROJECT = ` | ||
<Project Sdk="Microsoft.NET.Sdk.Web"> | ||
<PropertyGroup> | ||
<TargetFramework>net5.0</TargetFramework> | ||
<RootNamespace>MyTestApi.Test</RootNamespace> | ||
</PropertyGroup> | ||
<ItemGroup> | ||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" /> | ||
</ItemGroup> | ||
</Project>`; | ||
|
||
describe('import-projects generator', () => { | ||
let appTree: Tree; | ||
|
||
beforeEach(() => { | ||
appTree = createTreeWithEmptyWorkspace(); | ||
}); | ||
|
||
afterEach(() => { | ||
jest.resetAllMocks(); | ||
}); | ||
|
||
it('should run successfully if no new projects are found', async () => { | ||
jest.spyOn(utils, 'glob').mockResolvedValue([]); | ||
const promise = generator(appTree); | ||
const oldProjects = getProjects(appTree); | ||
await expect(promise).resolves.not.toThrow(); | ||
const newProjects = getProjects(appTree); | ||
expect(oldProjects).toEqual(newProjects); | ||
}); | ||
|
||
it('should run successfully if new projects are found', async () => { | ||
jest | ||
.spyOn(utils, 'glob') | ||
.mockImplementation((x) => | ||
Promise.resolve( | ||
x.startsWith('apps') ? ['apps/my-api/my-api.csproj'] : [], | ||
), | ||
); | ||
jest | ||
.spyOn(utils, 'findProjectFileInPath') | ||
.mockImplementation((x) => | ||
x.startsWith('apps') | ||
? Promise.resolve('apps/my-api/my-api.csproj') | ||
: Promise.reject(), | ||
); | ||
jest.spyOn(fs, 'readFileSync').mockReturnValue(MOCK_TEST_PROJECT); | ||
jest.spyOn(fs, 'writeFileSync').mockImplementation(() => null); | ||
appTree.write('apps/my-api/my-api.csproj', MOCK_API_PROJECT); | ||
const promise = generator(appTree); | ||
await expect(promise).resolves.not.toThrow(); | ||
expect(readProjectConfiguration(appTree, 'my-test-api')).toBeDefined(); | ||
}); | ||
|
||
it('should run add test target if test projects are found', async () => { | ||
jest | ||
.spyOn(utils, 'glob') | ||
.mockImplementation((x) => | ||
Promise.resolve( | ||
x.startsWith('apps') ? ['apps/my-api-test/my-api-test.csproj'] : [], | ||
), | ||
); | ||
jest | ||
.spyOn(utils, 'findProjectFileInPath') | ||
.mockImplementation((x) => | ||
x.startsWith('apps') | ||
? Promise.resolve('apps/my-api/my-api-test.csproj') | ||
: Promise.reject(), | ||
); | ||
jest.spyOn(fs, 'readFileSync').mockReturnValue(MOCK_TEST_PROJECT); | ||
jest.spyOn(fs, 'writeFileSync').mockImplementation(() => null); | ||
appTree.write('apps/my-api-test/my-api-test.csproj', MOCK_TEST_PROJECT); | ||
const promise = generator(appTree); | ||
await expect(promise).resolves.not.toThrow(); | ||
expect(readProjectConfiguration(appTree, 'my-test-api-test')).toBeDefined(); | ||
expect( | ||
readProjectConfiguration(appTree, 'my-test-api-test').targets.test, | ||
).toBeDefined(); | ||
expect( | ||
readProjectConfiguration(appTree, 'my-test-api-test').targets.serve, | ||
).not.toBeDefined(); | ||
}); | ||
}); |
105 changes: 105 additions & 0 deletions
105
packages/core/src/generators/import-projects/generator.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
import { | ||
addProjectConfiguration, | ||
formatFiles, | ||
getProjects, | ||
getWorkspaceLayout, | ||
names, | ||
NxJsonProjectConfiguration, | ||
ProjectConfiguration, | ||
Tree, | ||
} from '@nrwl/devkit'; | ||
|
||
import { basename, dirname } from 'path'; | ||
import { XmlDocument } from 'xmldoc'; | ||
|
||
import { glob, iterateChildrenByPath, NXDOTNET_TAG } from '@nx-dotnet/utils'; | ||
|
||
import { | ||
GetBuildExecutorConfiguration, | ||
GetLintExecutorConfiguration, | ||
GetServeExecutorConfig, | ||
GetTestExecutorConfig, | ||
} from '../../models'; | ||
import { manipulateXmlProjectFile } from '../utils/generate-project'; | ||
|
||
export default async function (host: Tree) { | ||
const projectFiles = await getProjectFilesInWorkspace(host); | ||
const existingProjectRoots = Array.from(getProjects(host).values()).map( | ||
(x) => x.root, | ||
); | ||
for (const projectFile of projectFiles.newLibs) { | ||
if (!existingProjectRoots.some((x) => projectFile.startsWith(x))) { | ||
await addNewDotnetProject(host, projectFile, false); | ||
console.log('Found new library', projectFile); | ||
} | ||
} | ||
for (const projectFile of projectFiles.newApps) { | ||
if (!existingProjectRoots.some((x) => projectFile.startsWith(x))) { | ||
await addNewDotnetProject(host, projectFile, true); | ||
console.log('Found new application', projectFile); | ||
} | ||
} | ||
return formatFiles(host); | ||
} | ||
|
||
async function addNewDotnetProject( | ||
host: Tree, | ||
projectFile: string, | ||
app: boolean, | ||
) { | ||
const rootNamespace = readRootNamespace(host, projectFile); | ||
const projectRoot = dirname(projectFile); | ||
const projectName = rootNamespace | ||
? names(rootNamespace).fileName.replace(/\./g, '-') | ||
: names(basename(projectRoot)).fileName; | ||
const configuration: ProjectConfiguration & NxJsonProjectConfiguration = { | ||
root: projectRoot, | ||
targets: { | ||
build: GetBuildExecutorConfiguration(projectRoot), | ||
lint: GetLintExecutorConfiguration(), | ||
}, | ||
tags: [NXDOTNET_TAG], | ||
projectType: app ? 'application' : 'library', | ||
}; | ||
const testProject = await checkIfTestProject(host, projectFile); | ||
if (app && !testProject) { | ||
configuration.targets.serve = GetServeExecutorConfig(); | ||
} | ||
if (testProject) { | ||
configuration.targets.test = GetTestExecutorConfig(); | ||
} | ||
addProjectConfiguration(host, projectName, configuration); | ||
await manipulateXmlProjectFile(host, { | ||
projectName, | ||
projectRoot, | ||
}); | ||
} | ||
|
||
async function getProjectFilesInWorkspace(host: Tree) { | ||
const { appsDir, libsDir } = getWorkspaceLayout(host); | ||
const newProjects = { | ||
newLibs: await glob(`${libsDir}/**/*.@(cs|fs|vb)proj`), | ||
newApps: [] as string[], | ||
}; | ||
if (libsDir !== appsDir) { | ||
newProjects.newApps = await glob(`${appsDir}/**/*.@(cs|fs|vb)proj`); | ||
} | ||
return newProjects; | ||
} | ||
|
||
function readRootNamespace(host: Tree, path: string): string | undefined { | ||
const xml = new XmlDocument(host.read(path)?.toString() as string); | ||
return xml.valueWithPath('PropertyGroup.RootNamespace'); | ||
} | ||
|
||
async function checkIfTestProject(host: Tree, path: string): Promise<boolean> { | ||
const xml = new XmlDocument(host.read(path)?.toString() as string); | ||
let isTestProject = false; | ||
await iterateChildrenByPath(xml, 'ItemGroup.PackageReference', (el) => { | ||
const pkg = el.attr['Include']; | ||
if (pkg === 'Microsoft.NET.Test.Sdk') { | ||
isTestProject = true; | ||
} | ||
}); | ||
return isTestProject; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"$schema": "http://json-schema.org/schema", | ||
"cli": "nx", | ||
"id": "@nx-dotnet/core:import-projects", | ||
"title": "Import Projects", | ||
"description": "Import existing .NET projects in C#, VB, or F# that are in your workspace's apps or libs directories. Simply move the projects into these folders, and then run `nx g @nx-dotnet/core:import-projects` to move them into Nx. Projects inside the apps directory will include a serve target, while projects inside libs will only contain build + lint targets.", | ||
"type": "object", | ||
"properties": {} | ||
} |
Oops, something went wrong.