diff --git a/projects/nx-verdaccio/src/plugin/targets/create-targets.ts b/projects/nx-verdaccio/src/plugin/targets/create-targets.ts index d5ae71e8..49bd8c57 100644 --- a/projects/nx-verdaccio/src/plugin/targets/create-targets.ts +++ b/projects/nx-verdaccio/src/plugin/targets/create-targets.ts @@ -9,6 +9,24 @@ import { } from './environment.targets'; import { getPkgTargets, isPkgProject } from './package.targets'; +/** + * Generates a project configuration partial including `targets` and `namedInputs`. + * + * If the project is an environment project, derived by `isEnvProject()`, + * It returns the results of `verdaccioTargets()`, `getEnvTargets()`, + *`updateEnvTargetNames()` under `targets`, and `namedInputs` + * + * If the project is a publishable project, derived by `isPkgProject()`, + * it returns the results of `getPkgTargets()`, and `namedInputs` + * + * Otherwise, it returns an empty object (as early exit) + * + * Additionally, it logs warnings for missing implicit dependencies in environment projects. + * + * @param projectConfiguration + * @param options + * @returns A partial project configuration with `targets` and `namedInputs`. + */ export function createProjectConfiguration( projectConfiguration: ProjectConfiguration, options: NxVerdaccioCreateNodeOptions @@ -28,9 +46,10 @@ export function createProjectConfiguration( } /** - * Unfortunately namedInputs are not picked up by tasks graph: Error: Input 'build-artifacts' is not defined - * When you pass your own namedInputs (like you would in a project.json file) via the inferred tasks plugin, the tasks pipeline ignores them and throws this error. - * Some Nx plugins use the default namedInput, probably for that reason, but I'm concerned that if developers change those inputs, it might lead to undesired behavior. + * When you pass your own `namedInputs` (like you would in a `project.json` file) + * via the inferred tasks plugin, the tasks pipeline ignores them and throws this error. + * Some Nx plugins use the default `namedInput`, probably for that reason, + * but I'm concerned that if developers change those inputs, it might lead to undesired behaviour. * @todo investigate if there is a way to pass namedInputs to the tasks graph */ const namedInputs: ProjectConfiguration['namedInputs'] = { diff --git a/projects/nx-verdaccio/src/plugin/targets/create-targets.unit-test.ts b/projects/nx-verdaccio/src/plugin/targets/create-targets.unit-test.ts new file mode 100644 index 00000000..a7ebe1ee --- /dev/null +++ b/projects/nx-verdaccio/src/plugin/targets/create-targets.unit-test.ts @@ -0,0 +1,249 @@ +import { beforeEach, describe, expect, type MockInstance } from 'vitest'; +import { type ProjectConfiguration } from '@nx/devkit'; + +import * as nxDevkitMockModule from '@nx/devkit'; + +import { createProjectConfiguration } from './create-targets'; +import { + TARGET_PACKAGE_INSTALL, + TARGET_PACKAGE_PUBLISH, +} from './package.targets'; +import { + TARGET_ENVIRONMENT_E2E, + TARGET_ENVIRONMENT_SETUP, + TARGET_ENVIRONMENT_INSTALL, + TARGET_ENVIRONMENT_TEARDOWN, + TARGET_ENVIRONMENT_BOOTSTRAP, + TARGET_ENVIRONMENT_PUBLISH_ONLY, + TARGET_ENVIRONMENT_VERDACCIO_STOP, + TARGET_ENVIRONMENT_VERDACCIO_START, +} from './environment.targets'; + +import { type NxVerdaccioCreateNodeOptions } from '../schema'; +import { type NormalizedCreateNodeOptions } from '../normalize-create-nodes-options'; + +import * as packageTargetsSpyModule from './package.targets'; +import * as environmentTargetsModule from './environment.targets'; +import * as normalizeCreateNodesSpyModule from './../normalize-create-nodes-options'; + +describe('createProjectConfiguration', (): void => { + const implicitDependencies = ['mock-implicit-dep']; + const projectConfiguration: ProjectConfiguration = { + root: 'mock-root', + name: 'unit-test-project', + targets: { build: {} }, + }; + const options: NxVerdaccioCreateNodeOptions = { + environments: { + targetNames: ['build'], + }, + }; + const normalizedOptions: NormalizedCreateNodeOptions = { + environments: { + targetNames: ['build'], + environmentsDir: './environments', + }, + packages: { + targetNames: ['test', 'lint'], + environmentsDir: './packages', + }, + }; + + vi.mock('@nx/devkit', () => { + return { + logger: { + warn: vi.fn(), + }, + }; + }); + + let normalizeCreateNodesOptionsSpy: MockInstance; + let isEnvProjectSpy: MockInstance; + let isPkgSpy: MockInstance; + let verdaccioTargetsSpy: MockInstance; + let getEnvTargetsSpy: MockInstance; + let updateEnvTargetNamesSpy: MockInstance; + + beforeEach((): void => { + normalizeCreateNodesOptionsSpy = vi + .spyOn(normalizeCreateNodesSpyModule, 'normalizeCreateNodesOptions') + .mockReturnValue(normalizedOptions); + isEnvProjectSpy = vi + .spyOn(environmentTargetsModule, 'isEnvProject') + .mockReturnValue(true); + isPkgSpy = vi + .spyOn(packageTargetsSpyModule, 'isPkgProject') + .mockReturnValue(true); + verdaccioTargetsSpy = vi.spyOn( + environmentTargetsModule, + 'verdaccioTargets' + ); + getEnvTargetsSpy = vi.spyOn(environmentTargetsModule, 'getEnvTargets'); + updateEnvTargetNamesSpy = vi.spyOn( + environmentTargetsModule, + 'updateEnvTargetNames' + ); + }); + + afterEach((): void => { + normalizeCreateNodesOptionsSpy.mockRestore(); + isEnvProjectSpy.mockRestore(); + isPkgSpy.mockRestore(); + verdaccioTargetsSpy.mockRestore(); + getEnvTargetsSpy.mockRestore(); + updateEnvTargetNamesSpy.mockRestore(); + }); + + it('should generate a config if isE2eProject and isPublishableProject are true', (): void => { + expect( + createProjectConfiguration(projectConfiguration, options) + ).toMatchObject({ + targets: expect.any(Object), + namedInputs: expect.any(Object), + }); + }); + + it('should generate a config if isE2eProject is true and isPublishableProject is false', (): void => { + isPkgSpy.mockReturnValue(false); + expect( + createProjectConfiguration(projectConfiguration, options) + ).toMatchObject({ + namedInputs: expect.any(Object), + targets: expect.any(Object), + }); + }); + + it('should generate a config if isE2eProject is false and isPublishableProject is true', (): void => { + isEnvProjectSpy.mockReturnValue(false); + expect( + createProjectConfiguration(projectConfiguration, options) + ).toMatchObject({ + targets: expect.any(Object), + }); + }); + + it('should generate targets if isE2eProject is true and isPublishableProject is false', (): void => { + isPkgSpy.mockReturnValue(false); + expect( + createProjectConfiguration(projectConfiguration, options)['targets'] + ).toMatchObject({ + build: expect.any(Object), + [TARGET_ENVIRONMENT_VERDACCIO_START]: expect.any(Object), + [TARGET_ENVIRONMENT_VERDACCIO_STOP]: expect.any(Object), + [TARGET_ENVIRONMENT_BOOTSTRAP]: expect.any(Object), + [TARGET_ENVIRONMENT_INSTALL]: expect.any(Object), + [TARGET_ENVIRONMENT_PUBLISH_ONLY]: expect.any(Object), + [TARGET_ENVIRONMENT_SETUP]: expect.any(Object), + [TARGET_ENVIRONMENT_TEARDOWN]: expect.any(Object), + [TARGET_ENVIRONMENT_E2E]: expect.any(Object), + }); + }); + + it('should generate targets if isE2eProject is false and isPublishableProject is true', (): void => { + expect( + createProjectConfiguration(projectConfiguration, options)['targets'] + ).toMatchObject({ + [TARGET_PACKAGE_PUBLISH]: expect.any(Object), + [TARGET_PACKAGE_INSTALL]: expect.any(Object), + }); + }); + + it('should return an empty object if isE2eProject and isPublishableProject are false', (): void => { + isEnvProjectSpy.mockReturnValue(false); + isPkgSpy.mockReturnValue(false); + expect( + createProjectConfiguration(projectConfiguration, options) + ).toStrictEqual({}); + }); + + it('should call normalizeCreateNodesOptions ones with projectConfiguration and options', (): void => { + createProjectConfiguration(projectConfiguration, options); + expect( + normalizeCreateNodesSpyModule.normalizeCreateNodesOptions + ).toHaveBeenCalledOnce(); + expect( + normalizeCreateNodesSpyModule.normalizeCreateNodesOptions + ).toHaveBeenCalledWith(options); + }); + + it('should call isEnvProject ones with projectConfiguration and environments', (): void => { + createProjectConfiguration(projectConfiguration, options); + expect(environmentTargetsModule.isEnvProject).toHaveBeenCalledOnce(); + expect(environmentTargetsModule.isEnvProject).toHaveBeenCalledWith( + projectConfiguration, + normalizedOptions['environments'] + ); + }); + + it('should call isPublishableProject ones with projectConfiguration and packages', (): void => { + createProjectConfiguration(projectConfiguration, options); + expect(packageTargetsSpyModule.isPkgProject).toHaveBeenCalledOnce(); + expect(packageTargetsSpyModule.isPkgProject).toHaveBeenCalledWith( + projectConfiguration, + normalizedOptions['packages'] + ); + }); + + it('should call verdaccioTargets ones with correct arguments', (): void => { + createProjectConfiguration(projectConfiguration, options); + expect(environmentTargetsModule.verdaccioTargets).toHaveBeenCalledOnce(); + expect(environmentTargetsModule.verdaccioTargets).toHaveBeenCalledWith( + projectConfiguration, + { environmentsDir: normalizedOptions.environments.environmentsDir } + ); + }); + + it('should call getEnvTargets ones with correct arguments', (): void => { + createProjectConfiguration(projectConfiguration, options); + expect(environmentTargetsModule.getEnvTargets).toHaveBeenCalledOnce(); + expect(environmentTargetsModule.getEnvTargets).toHaveBeenCalledWith( + projectConfiguration, + normalizedOptions.environments + ); + }); + + it('should call updateEnvTargetNames ones with correct arguments', (): void => { + createProjectConfiguration(projectConfiguration, options); + expect( + environmentTargetsModule.updateEnvTargetNames + ).toHaveBeenCalledOnce(); + expect(environmentTargetsModule.updateEnvTargetNames).toHaveBeenCalledWith( + projectConfiguration, + normalizedOptions.environments + ); + }); + + it('should log warn if isE2eProject is true and implicitDependencies are empty', (): void => { + createProjectConfiguration(projectConfiguration, options); + expect(nxDevkitMockModule.logger.warn).toHaveBeenCalledOnce(); + }); + + it('should not log warn if isE2eProject is true and implicitDependencies are given', (): void => { + createProjectConfiguration( + { + ...projectConfiguration, + implicitDependencies, + }, + options + ); + expect(nxDevkitMockModule.logger.warn).toHaveBeenCalledTimes(0); + }); + + it('should not log warn if isE2eProject is false and implicitDependencies are given', (): void => { + isEnvProjectSpy.mockReturnValue(false); + createProjectConfiguration( + { + ...projectConfiguration, + implicitDependencies, + }, + options + ); + expect(nxDevkitMockModule.logger.warn).toHaveBeenCalledTimes(0); + }); + + it('should not log warn if isE2eProject is false and implicitDependencies are not given', (): void => { + isEnvProjectSpy.mockReturnValue(false); + createProjectConfiguration(projectConfiguration, options); + expect(nxDevkitMockModule.logger.warn).toHaveBeenCalledTimes(0); + }); +}); diff --git a/projects/nx-verdaccio/src/plugin/targets/package.targets.ts b/projects/nx-verdaccio/src/plugin/targets/package.targets.ts index dc6047cd..28d1b08d 100644 --- a/projects/nx-verdaccio/src/plugin/targets/package.targets.ts +++ b/projects/nx-verdaccio/src/plugin/targets/package.targets.ts @@ -7,6 +7,15 @@ import { EXECUTOR_PACKAGE_NPM_INSTALL } from '../../executors/pkg-install/consta export const TARGET_PACKAGE_INSTALL = 'nxv-pkg-install'; export const TARGET_PACKAGE_PUBLISH = 'nxv-pkg-publish'; +/** + * Determines if the given project is a `publishable` package. + * A project qualifies as a `publishable` if it's of type 'library'. + * If tag filters are provided only projects passing the filter will return true. + * + * @param projectConfig + * @param options + * @returns `true` if the project is a publishable; otherwise, `false`. + */ export function isPkgProject( projectConfig: ProjectConfiguration, options: NormalizedCreateNodeOptions['packages'] @@ -29,6 +38,12 @@ export function isPkgProject( return true; } +/** + * Creates package-related targets for build pipelines. + * Includes `TARGET_PACKAGE_PUBLISH` and `TARGET_PACKAGE_INSTALL` target configurations. + * + * @returns A record of package targets with their configurations. + */ export function getPkgTargets(): Record { return { [TARGET_PACKAGE_PUBLISH]: { diff --git a/projects/nx-verdaccio/src/plugin/targets/package.targets.unit-test.ts b/projects/nx-verdaccio/src/plugin/targets/package.targets.unit-test.ts index d9903da1..dc53cd0d 100644 --- a/projects/nx-verdaccio/src/plugin/targets/package.targets.unit-test.ts +++ b/projects/nx-verdaccio/src/plugin/targets/package.targets.unit-test.ts @@ -1,5 +1,14 @@ import { describe, it, expect } from 'vitest'; -import { isPkgProject } from './package.targets'; + +import { + getPkgTargets, + isPkgProject, + TARGET_PACKAGE_INSTALL, + TARGET_PACKAGE_PUBLISH, +} from './package.targets'; + +import { EXECUTOR_PACKAGE_NPM_INSTALL } from '../../executors/pkg-install/constants'; +import { EXECUTOR_PACKAGE_NPM_PUBLISH } from '../../executors/pkg-publish/constants'; describe('isPkgProject', () => { it('should return true for projects with projectType is library', () => { @@ -58,3 +67,66 @@ describe('isPkgProject', () => { ).toBe(false); }); }); + +describe('getPkgTargets', (): void => { + + it('should generate PkgTargets', (): void => { + expect(getPkgTargets()).toMatchObject({ + [TARGET_PACKAGE_PUBLISH]: expect.any(Object), + [TARGET_PACKAGE_INSTALL]: expect.any(Object), + }); + }); + + it('should generate TARGET_PACKAGE_PUBLISH', (): void => { + expect(getPkgTargets()[TARGET_PACKAGE_PUBLISH]).toMatchObject({ + dependsOn: expect.any(Array), + executor: expect.any(String), + options: {}, + }); + }); + + it('should generate TARGET_PACKAGE_PUBLISH dependsOn', (): void => { + expect(getPkgTargets()[TARGET_PACKAGE_PUBLISH].dependsOn).toEqual([ + { target: 'build', params: 'forward' }, + { + projects: 'dependencies', + target: TARGET_PACKAGE_PUBLISH, + params: 'forward', + }, + ]); + }); + + it('should generate TARGET_PACKAGE_PUBLISH executor', (): void => { + expect(getPkgTargets()[TARGET_PACKAGE_PUBLISH].executor).matches( + new RegExp(`.+:${EXECUTOR_PACKAGE_NPM_PUBLISH}`) + ); + }); + + it('should generate TARGET_PACKAGE_INSTALL', (): void => { + expect(getPkgTargets()[TARGET_PACKAGE_INSTALL]).toMatchObject({ + dependsOn: expect.any(Array), + executor: expect.any(String), + options: {}, + }); + }); + + it('should generate TARGET_PACKAGE_INSTALL dependsOn', (): void => { + expect(getPkgTargets()[TARGET_PACKAGE_INSTALL].dependsOn).toEqual([ + { + target: TARGET_PACKAGE_PUBLISH, + params: 'forward', + }, + { + projects: 'dependencies', + target: TARGET_PACKAGE_INSTALL, + params: 'forward', + }, + ]); + }); + + it('should generate TARGET_PACKAGE_INSTALL executor', (): void => { + expect(getPkgTargets()[TARGET_PACKAGE_INSTALL].executor).matches( + new RegExp(`.+:${EXECUTOR_PACKAGE_NPM_INSTALL}`) + ); + }); +});