diff --git a/docs/core/Generators/import-projects.md b/docs/core/Generators/import-projects.md deleted file mode 100644 index ba656886..00000000 --- a/docs/core/Generators/import-projects.md +++ /dev/null @@ -1,5 +0,0 @@ -# @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. diff --git a/docs/core/index.md b/docs/core/index.md index 9ddf87c4..cd23bd7a 100644 --- a/docs/core/index.md +++ b/docs/core/index.md @@ -109,10 +109,6 @@ Restores NuGet packages and .NET tools used by the workspace Generate a .NET test project for an existing application or library -### [import-projects](./Generators/import-projects.md) - -Import existing projects into your Nx workspace - ### [add-swagger-target](./Generators/add-swagger-target.md) Add a swagger target to a webapi based project to extract swagger.json into a newly generated library project diff --git a/docs/index.md b/docs/index.md index 154a4b96..560deaf2 100644 --- a/docs/index.md +++ b/docs/index.md @@ -8,7 +8,7 @@ slug: / ## [@nx-dotnet/core](./core) - 7 Executors -- 11 Generators +- 10 Generators ## [@nx-dotnet/nx-ghpages](./nx-ghpages) diff --git a/nx.json b/nx.json index 1437d088..65ba7fe3 100644 --- a/nx.json +++ b/nx.json @@ -3,10 +3,6 @@ "affected": { "defaultBase": "master" }, - "workspaceLayout": { - "appsDir": "", - "libsDir": "" - }, "plugins": [ { "plugin": "@nx-dotnet/core", diff --git a/package.json b/package.json index 01a7ef66..5d8b2cd8 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "documentation:check": "ts-node ./tools/scripts/hooks/documentation.check.ts", "documentation": "nx g @nx-dotnet/nxdoc:generate-docs", "publish-dev": "ts-node tools/scripts/publish-dev", - "sandbox": "ts-node ./tools/scripts/sandbox.ts" + "sandbox": "ts-node ./tools/scripts/sandbox.ts", + "local-registry": "ts-node ./tools/scripts/local-registry/setup.ts" }, "private": false, "dependencies": { diff --git a/packages/core/generators.json b/packages/core/generators.json index 312caa90..fa07ffda 100644 --- a/packages/core/generators.json +++ b/packages/core/generators.json @@ -50,11 +50,6 @@ "description": "Generate a .NET test project for an existing application or library", "x-type": "library" }, - "import-projects": { - "factory": "./src/generators/import-projects/generator", - "schema": "./src/generators/import-projects/schema.json", - "description": "Import existing projects into your Nx workspace" - }, "add-swagger-target": { "factory": "./src/generators/add-swagger-target/add-swagger-target", "schema": "./src/generators/add-swagger-target/schema.json", diff --git a/packages/core/src/generators/import-projects/generator.spec.ts b/packages/core/src/generators/import-projects/generator.spec.ts deleted file mode 100644 index aec59e45..00000000 --- a/packages/core/src/generators/import-projects/generator.spec.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { getProjects, readProjectConfiguration, Tree } from '@nx/devkit'; -import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; - -import * as fs from 'fs'; - -import { DotNetClient, mockDotnetFactory } from '@nx-dotnet/dotnet'; -import * as utils from '@nx-dotnet/utils'; - -import * as mockedInitGenerator from '../init/generator'; -import generator from './generator'; - -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 = ` - - - net5.0 - MyTestApi - -`; - -const MOCK_TEST_PROJECT = ` - - - net5.0 - MyTestApi.Test - - - - -`; - -jest.mock('../init/generator', () => ({ - initGenerator: jest.fn(() => { - return Promise.resolve(jest.fn(() => Promise.resolve())); - }), -})); - -describe('import-projects generator', () => { - let tree: Tree; - let dotnetClient: DotNetClient; - - beforeEach(() => { - tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); - dotnetClient = new DotNetClient(mockDotnetFactory()); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it('should run successfully if no new projects are found', async () => { - jest.spyOn(utils, 'glob').mockResolvedValue([]); - const promise = generator(tree, null, dotnetClient); - const oldProjects = getProjects(tree); - await expect(promise).resolves.not.toThrow(); - const newProjects = getProjects(tree); - 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(new Error()), - ); - jest.spyOn(fs, 'readFileSync').mockReturnValue(MOCK_TEST_PROJECT); - jest.spyOn(fs, 'writeFileSync').mockImplementation(() => null); - tree.write('apps/my-api/my-api.csproj', MOCK_API_PROJECT); - const promise = generator(tree, null, dotnetClient); - await expect(promise).resolves.not.toThrow(); - expect(readProjectConfiguration(tree, '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(new Error()), - ); - jest.spyOn(fs, 'readFileSync').mockReturnValue(MOCK_TEST_PROJECT); - jest.spyOn(fs, 'writeFileSync').mockImplementation(() => null); - tree.write('apps/my-api-test/my-api-test.csproj', MOCK_TEST_PROJECT); - const promise = generator(tree, null, dotnetClient); - await expect(promise).resolves.not.toThrow(); - expect(readProjectConfiguration(tree, 'my-test-api-test')).toBeDefined(); - expect( - readProjectConfiguration(tree, 'my-test-api-test').targets?.test, - ).toBeDefined(); - expect( - readProjectConfiguration(tree, 'my-test-api-test').targets?.serve, - ).not.toBeDefined(); - }); - - it('should call init generator', async () => { - const initGenerator = ( - mockedInitGenerator as jest.Mocked - ).initGenerator; - - jest.spyOn(utils, 'glob').mockResolvedValue([]); - await generator(tree, null, dotnetClient); - expect(initGenerator).toHaveBeenCalledWith(tree, null, dotnetClient); - }); -}); diff --git a/packages/core/src/generators/import-projects/generator.ts b/packages/core/src/generators/import-projects/generator.ts deleted file mode 100644 index 066d5988..00000000 --- a/packages/core/src/generators/import-projects/generator.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { - addProjectConfiguration, - formatFiles, - getProjects, - getWorkspaceLayout, - joinPathFragments, - logger, - names, - ProjectConfiguration, - TargetConfiguration, - Tree, -} from '@nx/devkit'; - -import { basename, dirname } from 'path'; -import { XmlDocument } from 'xmldoc'; - -import { DotNetClient, dotnetFactory } from '@nx-dotnet/dotnet'; -import { glob, iterateChildrenByPath, projPattern } from '@nx-dotnet/utils'; - -import { - GetBuildExecutorConfiguration, - GetLintExecutorConfiguration, - GetServeExecutorConfig, - GetTestExecutorConfig, -} from '../../models'; -import { initGenerator } from '../init/generator'; - -export default async function ( - host: Tree, - options: null, // The second option is provided at runtime by Nx for options passed in to the generator. - dotnetClient = new DotNetClient(dotnetFactory()), -) { - const installTask = await initGenerator(host, null, dotnetClient); - - const projectFiles = await getProjectFilesInWorkspace(host); - const existingProjectJsonDirectories = getDirectoriesWithProjectJson(host); - for (const projectFile of projectFiles.newLibs) { - if ( - !existingProjectJsonDirectories.some((x) => - projectFile.startsWith(x + '/'), - ) - ) { - await addNewDotnetProject(host, projectFile, false); - logger.log('Found new library', projectFile); - } - } - for (const projectFile of projectFiles.newApps) { - if ( - !existingProjectJsonDirectories.some((x) => - projectFile.startsWith(x + '/'), - ) - ) { - await addNewDotnetProject(host, projectFile, true); - logger.log('Found new application', projectFile); - } - } - return async () => { - await installTask(); - await 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 & { - targets: Record; - } = { - root: projectRoot, - targets: { - build: GetBuildExecutorConfiguration(projectRoot), - lint: GetLintExecutorConfiguration(), - }, - 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); -} - -async function getProjectFilesInWorkspace(host: Tree) { - const { appsDir, libsDir } = getWorkspaceLayout(host); - const newProjects = { - newLibs: await glob(projPattern(libsDir)), - newApps: [] as string[], - }; - if (libsDir !== appsDir) { - newProjects.newApps = await glob(projPattern(appsDir)); - } - 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 { - 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; -} - -function getDirectoriesWithProjectJson(host: Tree) { - const nxProjects = getProjects(host); - const collected: string[] = []; - for (const proj of nxProjects.values()) { - if (host.exists(joinPathFragments(proj.root, 'project.json'))) { - collected.push(proj.root); - } - } - return collected; -} diff --git a/packages/core/src/generators/import-projects/schema.json b/packages/core/src/generators/import-projects/schema.json deleted file mode 100644 index 5179be83..00000000 --- a/packages/core/src/generators/import-projects/schema.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "$schema": "http://json-schema.org/schema", - "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": {} -} diff --git a/packages/core/src/generators/move/generator.spec.ts b/packages/core/src/generators/move/generator.spec.ts index 8eda7c17..b59bf3ea 100644 --- a/packages/core/src/generators/move/generator.spec.ts +++ b/packages/core/src/generators/move/generator.spec.ts @@ -7,12 +7,23 @@ import { names, offsetFromRoot, ProjectConfiguration, + ProjectGraph, } from '@nx/devkit'; import { uniq } from '@nx/plugin/testing'; import generator from './generator'; import { basename } from 'path'; +let graph: ProjectGraph = { + dependencies: {}, + nodes: {}, +}; + +jest + // eslint-disable-next-line @typescript-eslint/no-var-requires + .spyOn(require('@nx/devkit'), 'createProjectGraphAsync') + .mockImplementation(() => Promise.resolve(graph)); + describe('move generator', () => { let tree: Tree; @@ -20,6 +31,10 @@ describe('move generator', () => { tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs', }); + graph = { + dependencies: {}, + nodes: {}, + }; }); it('should move simple projects successfully', async () => { @@ -107,12 +122,22 @@ describe('move generator', () => { new RegExp(`^${joinPathFragments(relativeToRoot, 'node_modules')}.*`), ); }); + + it('should work for inferred projects', async () => { + const { project, root: source } = makeSimpleProject(tree, 'app', 'a/b'); + tree.delete(joinPathFragments(source, 'project.json')); + const destination = joinPathFragments('a', 'b', 'c', uniq('app')); + await generator(tree, { projectName: project, destination }); + expect( + tree.exists(joinPathFragments(destination, 'project.json')), + ).toBeFalsy(); + }); }); function makeSimpleProject(tree: Tree, type: 'app' | 'lib', path?: string) { const project = uniq(type); const root = joinPathFragments(`${type}s`, path ?? '', project); - addProjectConfiguration(tree, project, { + const configuration: ProjectConfiguration = { root, sourceRoot: joinPathFragments(root, 'src'), projectType: type === 'app' ? 'application' : 'library', @@ -122,7 +147,14 @@ function makeSimpleProject(tree: Tree, type: 'app' | 'lib', path?: string) { outputs: [`{workspaceRoot}/dist/${root}`], }, }, - }); + }; + addProjectConfiguration(tree, project, configuration); + graph.nodes[project] = { + data: configuration, + name: project, + type, + }; + tree.write(joinPathFragments(root, 'readme.md'), 'contents'); return { project, root }; } diff --git a/packages/core/src/generators/move/generator.ts b/packages/core/src/generators/move/generator.ts index 0df10d05..c7ceaed5 100644 --- a/packages/core/src/generators/move/generator.ts +++ b/packages/core/src/generators/move/generator.ts @@ -1,5 +1,6 @@ import { addProjectConfiguration, + createProjectGraphAsync, formatFiles, getWorkspaceLayout, joinPathFragments, @@ -22,12 +23,22 @@ type NormalizedSchema = { destinationProject: string; }; -function normalizeOptions( +async function getCurrentProjectConfiguration( + projectName: string, +): Promise { + return await ( + await createProjectGraphAsync() + ).nodes[projectName].data; +} + +async function normalizeOptions( tree: Tree, options: MoveGeneratorSchema, -): NormalizedSchema { +): Promise { const { appsDir, libsDir } = getWorkspaceLayout(tree); - const currentRoot = readProjectConfiguration(tree, options.projectName).root; + const { root: currentRoot } = await getCurrentProjectConfiguration( + options.projectName, + ); let destinationRoot = options.destination; if (!options.relativeToRoot) { if (currentRoot.startsWith(appsDir)) { @@ -55,28 +66,45 @@ function normalizeOptions( } export default async function (tree: Tree, options: MoveGeneratorSchema) { - const normalizedOptions = normalizeOptions(tree, options); - const config = readProjectConfiguration( - tree, - normalizedOptions.currentProject, - ); - config.root = normalizedOptions.destinationRoot; - config.name = normalizedOptions.destinationProject; - removeProjectConfiguration(tree, normalizedOptions.currentProject); + const normalizedOptions = await normalizeOptions(tree, options); + const writeNewProjectJson = updateProjectJson(tree, normalizedOptions); + renameDirectory( tree, normalizedOptions.currentRoot, normalizedOptions.destinationRoot, ); - addProjectConfiguration( - tree, - options.projectName, - transformConfiguration(tree, config, normalizedOptions), - ); + + writeNewProjectJson(); + updateXmlReferences(tree, normalizedOptions); await formatFiles(tree); } +function updateProjectJson(tree: Tree, normalizedOptions: NormalizedSchema) { + try { + const config = readProjectConfiguration( + tree, + normalizedOptions.currentProject, + ); + config.root = normalizedOptions.destinationRoot; + config.name = normalizedOptions.destinationProject; + removeProjectConfiguration(tree, normalizedOptions.currentProject); + return () => { + addProjectConfiguration( + tree, + normalizedOptions.destinationProject, + transformConfiguration(tree, config, normalizedOptions), + ); + }; + } catch { + // There was no project.json, so dont add one. + return () => { + /* its fine */ + }; + } +} + function transformConfiguration( tree: Tree, config: ProjectConfiguration, diff --git a/packages/core/src/generators/utils/generate-project.ts b/packages/core/src/generators/utils/generate-project.ts index fbd6ab03..2034fe68 100644 --- a/packages/core/src/generators/utils/generate-project.ts +++ b/packages/core/src/generators/utils/generate-project.ts @@ -6,7 +6,6 @@ import { joinPathFragments, names, normalizePath, - ProjectConfiguration, ProjectType, Tree, } from '@nx/devkit'; @@ -20,7 +19,7 @@ import { dotnetNewOptions, KnownDotnetTemplates, } from '@nx-dotnet/dotnet'; -import { isDryRun, resolve } from '@nx-dotnet/utils'; +import { isDryRun, isNxCrystalEnabled, resolve } from '@nx-dotnet/utils'; import { GetBuildExecutorConfiguration, @@ -190,25 +189,21 @@ export async function GenerateProject( projectType, ); - const projectConfiguration: ProjectConfiguration = { - root: normalizedOptions.projectRoot, - projectType: projectType, - sourceRoot: `${normalizedOptions.projectRoot}`, - targets: { - build: GetBuildExecutorConfiguration(normalizedOptions.projectRoot), - ...(projectType === 'application' - ? { serve: GetServeExecutorConfig() } - : {}), - lint: GetLintExecutorConfiguration(), - }, - tags: normalizedOptions.parsedTags, - }; - - addProjectConfiguration( - host, - normalizedOptions.projectName, - projectConfiguration, - ); + if (!isNxCrystalEnabled()) { + addProjectConfiguration(host, normalizedOptions.projectName, { + root: normalizedOptions.projectRoot, + projectType: projectType, + sourceRoot: `${normalizedOptions.projectRoot}`, + targets: { + build: GetBuildExecutorConfiguration(normalizedOptions.projectRoot), + ...(projectType === 'application' + ? { serve: GetServeExecutorConfig() } + : {}), + lint: GetLintExecutorConfiguration(), + }, + tags: normalizedOptions.parsedTags, + }); + } const newParams: dotnetNewOptions = { language: normalizedOptions.language, @@ -232,7 +227,7 @@ export async function GenerateProject( if (!isDryRun()) { addToSolutionFile( host, - projectConfiguration.root, + normalizedOptions.projectRoot, dotnetClient, normalizedOptions.solutionFile, ); diff --git a/packages/core/src/generators/utils/generate-test-project.ts b/packages/core/src/generators/utils/generate-test-project.ts index bd5d5b45..4d5339e9 100644 --- a/packages/core/src/generators/utils/generate-test-project.ts +++ b/packages/core/src/generators/utils/generate-test-project.ts @@ -1,7 +1,11 @@ import { addProjectConfiguration, names, Tree } from '@nx/devkit'; import { DotNetClient, dotnetNewOptions } from '@nx-dotnet/dotnet'; -import { findProjectFileInPath, isDryRun } from '@nx-dotnet/utils'; +import { + findProjectFileInPath, + isDryRun, + isNxCrystalEnabled, +} from '@nx-dotnet/utils'; import { GetBuildExecutorConfiguration, @@ -42,17 +46,19 @@ export async function GenerateTestProject( const testRoot = schema.projectRoot + separator + suffix; const testProjectName = schema.projectName + separator + suffix; - addProjectConfiguration(host, testProjectName, { - root: testRoot, - projectType: schema.projectType, - sourceRoot: `${testRoot}`, - targets: { - build: GetBuildExecutorConfiguration(testRoot), - test: GetTestExecutorConfig(), - lint: GetLintExecutorConfiguration(), - }, - tags: schema.parsedTags, - }); + if (!isNxCrystalEnabled()) { + addProjectConfiguration(host, testProjectName, { + root: testRoot, + projectType: schema.projectType, + sourceRoot: `${testRoot}`, + targets: { + build: GetBuildExecutorConfiguration(testRoot), + test: GetTestExecutorConfig(), + lint: GetLintExecutorConfiguration(), + }, + tags: schema.parsedTags, + }); + } const newParams: dotnetNewOptions = { language: schema.language, diff --git a/packages/core/src/graph/__snapshots__/create-nodes.spec.ts.snap b/packages/core/src/graph/__snapshots__/create-nodes.spec.ts.snap index 2a62e6e8..46db933c 100644 --- a/packages/core/src/graph/__snapshots__/create-nodes.spec.ts.snap +++ b/packages/core/src/graph/__snapshots__/create-nodes.spec.ts.snap @@ -2,11 +2,15 @@ exports[`infer-project should generate build, lint, serve targets for projects 1`] = ` { + "cache": true, "configurations": { "production": { "configuration": "Release", }, }, + "dependsOn": [ + "^build", + ], "executor": "@nx-dotnet/core:build", "options": { "configuration": "Debug", @@ -15,13 +19,19 @@ exports[`infer-project should generate build, lint, serve targets for projects 1 "outputs": [ "{workspaceRoot}/dist/libs/api", "{workspaceRoot}/dist/intermediates/libs/api", + "{projectRoot}/bin", + "{projectRoot}/obj", ], } `; exports[`infer-project should generate build, lint, serve targets for projects 2`] = ` { + "cache": true, "executor": "@nx-dotnet/core:format", + "inputs": [ + "{projectRoot}/**/*.{cs,fs,vb}", + ], } `; @@ -41,8 +51,13 @@ exports[`infer-project should generate build, lint, serve targets for projects 3 exports[`infer-project should generate test target for test projects 1`] = ` { + "cache": true, + "dependsOn": [ + "build", + ], "executor": "@nx-dotnet/core:test", "options": { + "noBuild": true, "testProject": undefined, }, } diff --git a/packages/core/src/graph/create-dependencies.ts b/packages/core/src/graph/create-dependencies.ts index 4b338f40..796a259c 100644 --- a/packages/core/src/graph/create-dependencies.ts +++ b/packages/core/src/graph/create-dependencies.ts @@ -18,19 +18,16 @@ import { // It used to only consist of the context, but now it also includes the options. // The options were inserted as the first parameter, and the context was moved to the second. // The following types are used to support both signatures. -type CreateDependenciesV16 = ( - ctx: Parameters[1], - _: undefined, -) => ReturnType; - -type CreateDependenciesCompat = ( - p0: - | Parameters>[0] - | Parameters[0], - p1: - | Parameters>[1] - | Parameters[1], -) => ReturnType>; +type CreateDependenciesCompat = { + ( + ctx: CreateDependenciesContext, + _: undefined, + ): ReturnType>; + ( + opts: Parameters>[0], + ctx: CreateDependenciesContext, + ): ReturnType>; +}; export const createDependencies: CreateDependenciesCompat = ( ctxOrOpts: CreateDependenciesContext | NxDotnetConfig | undefined, diff --git a/packages/core/src/graph/create-nodes.ts b/packages/core/src/graph/create-nodes.ts index 6e526b3b..d1eec5ff 100644 --- a/packages/core/src/graph/create-nodes.ts +++ b/packages/core/src/graph/create-nodes.ts @@ -28,28 +28,57 @@ export const registerProjectTargets = ( ) => { const targets: Record = {}; const { inferredTargets } = opts; - if (inferredTargets !== false) { - const projectFileContents = readFileSync( - resolve(workspaceRoot, projectFile), - 'utf8', - ); - if ( - projectFileContents.includes('Microsoft.NET.Test.Sdk') && - inferredTargets.test - ) { - targets[inferredTargets.test] = GetTestExecutorConfig(); - } - if (inferredTargets.build) { - targets[inferredTargets.build] = GetBuildExecutorConfiguration( - dirname(projectFile), - ); - } - if (inferredTargets.lint) { - targets[inferredTargets.lint] = GetLintExecutorConfiguration(); - } - if (inferredTargets.serve) { - targets[inferredTargets.serve] = GetServeExecutorConfig(); - } + if (inferredTargets === false) { + return {}; + } + + const projectFileContents = readFileSync( + resolve(workspaceRoot, projectFile), + 'utf8', + ); + + if ( + projectFileContents.includes('Microsoft.NET.Test.Sdk') && + inferredTargets.test + ) { + const { targetName, ...extraOptions } = + typeof inferredTargets.test === 'string' + ? { targetName: inferredTargets.test } + : inferredTargets.test; + targets[targetName] = { + ...GetTestExecutorConfig(), + ...extraOptions, + }; + } + if (inferredTargets.build) { + const { targetName, ...extraOptions } = + typeof inferredTargets.build === 'string' + ? { targetName: inferredTargets.build } + : inferredTargets.build; + targets[targetName] = { + ...GetBuildExecutorConfiguration(dirname(projectFile)), + ...extraOptions, + }; + } + if (inferredTargets.lint) { + const { targetName, ...extraOptions } = + typeof inferredTargets.lint === 'string' + ? { targetName: inferredTargets.lint } + : inferredTargets.lint; + targets[targetName] = { + ...GetLintExecutorConfiguration(), + ...extraOptions, + }; + } + if (inferredTargets.serve) { + const { targetName, ...extraOptions } = + typeof inferredTargets.serve === 'string' + ? { targetName: inferredTargets.serve } + : inferredTargets.serve; + targets[targetName] = { + ...GetServeExecutorConfig(), + ...extraOptions, + }; } return targets; }; @@ -84,8 +113,9 @@ export const createNodes: CreateNodesCompat = [ `**/{${projectFilePatterns.join(',')}}`, ( file: string, - ctxOrOpts: CreateNodesContext | NxDotnetConfigV2 | undefined, - maybeCtx: CreateNodesContext | undefined, + // We read the config in the function to ensure it's always up to date / compatible. + // ctxOrOpts: CreateNodesContext | NxDotnetConfigV2 | undefined, + // maybeCtx: CreateNodesContext | undefined, ) => { const options = readConfig(); diff --git a/packages/core/src/models/build-executor-configuration.ts b/packages/core/src/models/build-executor-configuration.ts index 59fed9a8..719d3e7c 100644 --- a/packages/core/src/models/build-executor-configuration.ts +++ b/packages/core/src/models/build-executor-configuration.ts @@ -15,11 +15,15 @@ export function GetBuildExecutorConfiguration( : [ `{workspaceRoot}/dist/${projectRoot}`, `{workspaceRoot}/dist/intermediates/${projectRoot}`, + `{projectRoot}/bin`, + `{projectRoot}/obj`, ]; return { executor: '@nx-dotnet/core:build', outputs, + cache: true, + dependsOn: ['^build'], options: { configuration: 'Debug', noDependencies: true, diff --git a/packages/core/src/models/lint-executor-configuration.ts b/packages/core/src/models/lint-executor-configuration.ts index 8a91161f..86929bfc 100644 --- a/packages/core/src/models/lint-executor-configuration.ts +++ b/packages/core/src/models/lint-executor-configuration.ts @@ -3,5 +3,7 @@ import { TargetConfiguration } from '@nx/devkit'; export function GetLintExecutorConfiguration(): TargetConfiguration { return { executor: '@nx-dotnet/core:format', + cache: true, + inputs: ['{projectRoot}/**/*.{cs,fs,vb}'], }; } diff --git a/packages/core/src/models/test-executor-configuration.ts b/packages/core/src/models/test-executor-configuration.ts index b621f2fc..89d9d427 100644 --- a/packages/core/src/models/test-executor-configuration.ts +++ b/packages/core/src/models/test-executor-configuration.ts @@ -1,23 +1,21 @@ import { TargetConfiguration } from '@nx/devkit'; +import { TestExecutorSchema } from '../executors/test/schema'; export function GetTestExecutorConfig( projectName?: string, ): TestTargetConfiguration { return { executor: '@nx-dotnet/core:test', + cache: true, + dependsOn: ['build'], options: { testProject: projectName, + noBuild: true, }, }; } -export interface TestTargetConfiguration extends TargetConfiguration { - options: { - /** - * If null, implicitly this must be the test project. - * Else, run this target on the testProject instead of - * the executor's target. - */ - testProject?: string; - }; -} +export type TestTargetConfiguration = TargetConfiguration & { + executor: '@nx-dotnet/core:test'; + options: TestExecutorSchema; +}; diff --git a/packages/utils/src/lib/models/nx-dotnet-config.interface.ts b/packages/utils/src/lib/models/nx-dotnet-config.interface.ts index 92608591..19d2edc5 100644 --- a/packages/utils/src/lib/models/nx-dotnet-config.interface.ts +++ b/packages/utils/src/lib/models/nx-dotnet-config.interface.ts @@ -1,3 +1,4 @@ +import { TargetConfiguration } from '@nx/devkit'; import { ModuleBoundaries } from './nx'; export interface NxDotnetConfigV1 { @@ -31,11 +32,15 @@ export interface NxDotnetConfigV1 { inferProjects?: boolean; } +type PluginTargetConfiguration = TargetConfiguration & { + targetName: string; +}; + type ConfiguredTargets = { - build: string | false; - lint: string | false; - serve: string | false; - test: string | false; + build: PluginTargetConfiguration | string | false; + lint: PluginTargetConfiguration | string | false; + serve: PluginTargetConfiguration | string | false; + test: PluginTargetConfiguration | string | false; }; export type NxDotnetConfigV2 = Omit & { diff --git a/packages/utils/src/lib/utility-functions/workspace.ts b/packages/utils/src/lib/utility-functions/workspace.ts index 4c6c61cd..5230e76c 100644 --- a/packages/utils/src/lib/utility-functions/workspace.ts +++ b/packages/utils/src/lib/utility-functions/workspace.ts @@ -2,6 +2,7 @@ import { DependencyType, getProjects, normalizePath as nxNormalizePath, + NX_VERSION, ProjectConfiguration, ProjectsConfigurations, RawProjectGraphDependency, @@ -11,6 +12,7 @@ import { import { readFileSync } from 'fs'; import { dirname, relative, resolve } from 'path'; +import { lt } from 'semver'; import { XmlDocument, XmlElement } from 'xmldoc'; import { findProjectFileInPath, findProjectFileInPathSync, glob } from './glob'; @@ -192,6 +194,17 @@ export function inlineNxTokens(value: string, project: ProjectConfiguration) { return value.replace('{projectName}', project.name as string); } +export function isNxCrystalEnabled() { + // not the default + if (lt(NX_VERSION, '18.0.0')) { + return process.env.NX_PCV3 === 'true' || process.env.NX_CRYSTAL === 'true'; + } + // should be on by default + return !( + process.env.NX_PCV3 === 'false' || process.env.NX_CRYSTAL === 'false' + ); +} + function tryGetXmlDocument(file: string): XmlDocument | null { try { return new XmlDocument(readFileSync(file).toString()); diff --git a/smoke/core/tests/nx-dotnet.spec.ts b/smoke/core/tests/nx-dotnet.spec.ts index 0e085032..f1c888fc 100644 --- a/smoke/core/tests/nx-dotnet.spec.ts +++ b/smoke/core/tests/nx-dotnet.spec.ts @@ -41,26 +41,29 @@ function execAsync(command: string, opts: ExecOptions): Promise { } describe('nx-dotnet smoke', () => { - beforeAll(async () => { - ({ name: smokeDirectory, removeCallback: cleanup } = dirSync({ - unsafeCleanup: true, - })); - - await execAsync( - 'npx create-nx-workspace@latest test --preset ts --nxCloud false', - { - cwd: smokeDirectory, - env: process.env, - }, - ); - - await execAsync('git init', await execAsyncOptions()); - - await execAsync( - 'npm i --save-dev @nx-dotnet/core', - await execAsyncOptions(), - ); - }, 20 * 60 * 1000); // 20 minutes + beforeAll( + async () => { + ({ name: smokeDirectory, removeCallback: cleanup } = dirSync({ + unsafeCleanup: true, + })); + + await execAsync( + 'npx create-nx-workspace@latest test --preset ts --nxCloud skip', + { + cwd: smokeDirectory, + env: process.env, + }, + ); + + await execAsync('git init', await execAsyncOptions()); + + await execAsync( + 'npm i --save-dev @nx-dotnet/core', + await execAsyncOptions(), + ); + }, + 20 * 60 * 1000, + ); // 20 minutes afterAll(async () => { cleanup(); diff --git a/tools/scripts/local-registry/setup.ts b/tools/scripts/local-registry/setup.ts index 2eee5dab..9fdf83c3 100644 --- a/tools/scripts/local-registry/setup.ts +++ b/tools/scripts/local-registry/setup.ts @@ -49,3 +49,7 @@ export function killVerdaccioInstance() { process.on('SIGINT', () => { killVerdaccioInstance(); }); + +if (require.main === module) { + startCleanVerdaccioInstance(); +} diff --git a/tools/scripts/publish-dev/index.ts b/tools/scripts/publish-dev/index.ts index cf382e19..779cf105 100644 --- a/tools/scripts/publish-dev/index.ts +++ b/tools/scripts/publish-dev/index.ts @@ -3,7 +3,7 @@ import { publishAll } from '../publish-all'; import * as parser from 'yargs-parser'; -export function main(version: string) { +export async function main(version: string) { const rootPkg = readJson('package.json'); const [next, tagSpec]: [string, string | null] = rootPkg.version.startsWith( version, @@ -15,7 +15,7 @@ export function main(version: string) { rev = rev === 'NaN' ? '0' : rev; const newVersion = `${next}-${tag}.${rev}`; - publishAll(newVersion, tag); + return publishAll(newVersion, tag); } if (require.main === module) { @@ -27,5 +27,7 @@ if (require.main === module) { if (!version) { throw new Error('Version is required'); } - main(version); + main(version) + .then(() => process.exit(0)) + .catch(() => process.exit(1)); } diff --git a/tools/scripts/sandbox.ts b/tools/scripts/sandbox.ts index 0dec7bcd..5080dcab 100644 --- a/tools/scripts/sandbox.ts +++ b/tools/scripts/sandbox.ts @@ -12,11 +12,13 @@ import { startCleanVerdaccioInstance } from './local-registry/setup'; const sandboxDirectory = join(__dirname, '../../tmp/sandbox'); -export function setup() { +export async function setup() { copySync('.npmrc.local', '.npmrc'); try { - startCleanVerdaccioInstance(); - } catch { + await startCleanVerdaccioInstance(); + } catch (E) { + throw E; + // Its ok. } execSync('ts-node ./tools/scripts/publish-all 99.99.99 local', { @@ -29,28 +31,29 @@ export function setup() { } if (require.main === module) { - setup(); - if (existsSync(sandboxDirectory)) { - removeSync(sandboxDirectory); - } - ensureDirSync(dirname(sandboxDirectory)); - execSync( - `npx create-nx-workspace@latest ${basename( - sandboxDirectory, - )} --preset empty --no-nxCloud --packageManager yarn`, - { - cwd: dirname(sandboxDirectory), - stdio: 'inherit', - }, - ); - copySync('.npmrc.local', join(sandboxDirectory, '.npmrc')); - getWorkspacePackages() - .then((pkgs) => { - execSync(`yarn add --dev ${pkgs}`, { - cwd: sandboxDirectory, - stdio: 'inherit', + setup() + .then(() => { + if (existsSync(sandboxDirectory)) { + removeSync(sandboxDirectory); + } + ensureDirSync(dirname(sandboxDirectory)); + execSync( + `npx create-nx-workspace@latest ${basename( + sandboxDirectory, + )} --preset empty --no-nxCloud --packageManager yarn`, + { + cwd: dirname(sandboxDirectory), + stdio: 'inherit', + }, + ); + copySync('.npmrc.local', join(sandboxDirectory, '.npmrc')); + getWorkspacePackages().then((pkgs) => { + execSync(`yarn add --dev ${pkgs}`, { + cwd: sandboxDirectory, + stdio: 'inherit', + }); + console.log('Sandbox created at', resolve(sandboxDirectory)); }); - console.log('Sandbox created at', resolve(sandboxDirectory)); }) .catch((err) => { console.error(err);