From 5911f604f83037e37714d304c7c8b00671576bd9 Mon Sep 17 00:00:00 2001 From: AgentEnder Date: Mon, 11 Sep 2023 10:54:50 -0400 Subject: [PATCH] feat(devkit): update `parseTargetString` to allow referencing targets on the current project easily --- docs/generated/devkit/parseTargetString.md | 24 +++++++++++ .../src/executors/parse-target-string.spec.ts | 41 +++++++++++++++++++ .../src/executors/parse-target-string.ts | 39 +++++++++++++++++- packages/nx/src/devkit-internals.ts | 1 + 4 files changed, 103 insertions(+), 2 deletions(-) diff --git a/docs/generated/devkit/parseTargetString.md b/docs/generated/devkit/parseTargetString.md index de6b54560d7d8..aaf04f01fab72 100644 --- a/docs/generated/devkit/parseTargetString.md +++ b/docs/generated/devkit/parseTargetString.md @@ -35,3 +35,27 @@ parseTargetString('proj:test:production', graph); // returns { project: "proj", #### Returns [`Target`](../../devkit/documents/Target) + +▸ **parseTargetString**(`targetString`, `ctx`): [`Target`](../../devkit/documents/Target) + +Parses a target string into {project, target, configuration}. Passing a full +[ExecutorContext](../../devkit/documents/ExecutorContext) enables the targetString to reference the current project. + +Examples: + +```typescript +parseTargetString('test', executorContext); // returns { project: "proj", target: "test" } +parseTargetString('proj:test', executorContext); // returns { project: "proj", target: "test" } +parseTargetString('proj:test:production', executorContext); // returns { project: "proj", target: "test", configuration: "production" } +``` + +#### Parameters + +| Name | Type | +| :------------- | :---------------------------------------------------------- | +| `targetString` | `string` | +| `ctx` | [`ExecutorContext`](../../devkit/documents/ExecutorContext) | + +#### Returns + +[`Target`](../../devkit/documents/Target) diff --git a/packages/devkit/src/executors/parse-target-string.spec.ts b/packages/devkit/src/executors/parse-target-string.spec.ts index 9629db9469380..3ce6fd0b48182 100644 --- a/packages/devkit/src/executors/parse-target-string.spec.ts +++ b/packages/devkit/src/executors/parse-target-string.spec.ts @@ -1,6 +1,7 @@ import { parseTargetString, targetToTargetString } from './parse-target-string'; import * as splitTarget from 'nx/src/utils/split-target'; +import { ExecutorContext } from 'nx/src/devkit-exports'; const cases = [ { input: 'one:two', expected: { project: 'one', target: 'two' } }, @@ -15,12 +16,52 @@ const cases = [ ]; describe('parseTargetString', () => { + const mockContext: ExecutorContext = { + projectName: 'my-project', + cwd: '/virtual', + root: '/virtual', + isVerbose: false, + projectGraph: { + nodes: { + 'my-project': { + type: 'lib', + name: 'my-project', + data: { root: '/packages/my-project' }, + }, + 'other-project': { + type: 'lib', + name: 'other-project', + data: { root: '/packages/other-project' }, + }, + }, + dependencies: {}, + externalNodes: {}, + version: '', + }, + }; + it.each(cases)('$input -> $expected', ({ input, expected }) => { jest .spyOn(splitTarget, 'splitTarget') .mockReturnValueOnce(Object.values(expected) as [string]); expect(parseTargetString(input, null)).toEqual(expected); }); + + it('should support reading project from ExecutorContext', () => { + expect(parseTargetString('build', mockContext)).toEqual({ + project: 'my-project', + target: 'build', + }); + expect(parseTargetString('build:production', mockContext)).toEqual({ + project: 'my-project', + target: 'build', + configuration: 'production', + }); + expect(parseTargetString('other-project:build', mockContext)).toEqual({ + project: 'other-project', + target: 'build', + }); + }); }); describe('targetToTargetString', () => { diff --git a/packages/devkit/src/executors/parse-target-string.ts b/packages/devkit/src/executors/parse-target-string.ts index d7b0c12689741..44c0955dd7790 100644 --- a/packages/devkit/src/executors/parse-target-string.ts +++ b/packages/devkit/src/executors/parse-target-string.ts @@ -1,11 +1,15 @@ import type { Target } from 'nx/src/command-line/run/run'; import type { ProjectGraph } from 'nx/src/config/project-graph'; +import type { ExecutorContext } from 'nx/src/devkit-exports'; + import { requireNx } from '../../nx'; -let { readCachedProjectGraph, splitTarget } = requireNx(); +let { readCachedProjectGraph, splitTarget, splitByColons } = requireNx(); // TODO: Remove this in Nx 18 when Nx 16.7.0 is no longer supported splitTarget = splitTarget ?? require('nx/src/utils/split-target').splitTarget; +splitByColons = + splitByColons ?? ((s: string) => s.split(':') as [string, ...string[]]); /** * @deprecated(v17) A project graph should be passed to parseTargetString for best accuracy. @@ -26,10 +30,30 @@ export function parseTargetString( targetString: string, projectGraph: ProjectGraph ): Target; +/** + * Parses a target string into {project, target, configuration}. Passing a full + * {@link ExecutorContext} enables the targetString to reference the current project. + * + * Examples: + * ```typescript + * parseTargetString("test", executorContext) // returns { project: "proj", target: "test" } + * parseTargetString("proj:test", executorContext) // returns { project: "proj", target: "test" } + * parseTargetString("proj:test:production", executorContext) // returns { project: "proj", target: "test", configuration: "production" } + * ``` + */ export function parseTargetString( targetString: string, - projectGraph?: ProjectGraph + ctx: ExecutorContext +): Target; +export function parseTargetString( + targetString: string, + projectGraphOrCtx?: ProjectGraph | ExecutorContext ): Target { + let projectGraph = + projectGraphOrCtx && 'projectGraph' in projectGraphOrCtx + ? projectGraphOrCtx.projectGraph + : (projectGraphOrCtx as ProjectGraph); + if (!projectGraph) { try { projectGraph = readCachedProjectGraph(); @@ -37,10 +61,21 @@ export function parseTargetString( projectGraph = { nodes: {} } as any; } } + + const [maybeProject] = splitByColons(targetString); + if ( + !projectGraph.nodes[maybeProject] && + projectGraphOrCtx && + 'projectName' in projectGraphOrCtx + ) { + targetString = `${projectGraphOrCtx.projectName}:${targetString}`; + } + const [project, target, configuration] = splitTarget( targetString, projectGraph ); + if (!project || !target) { throw new Error(`Invalid Target String: ${targetString}`); } diff --git a/packages/nx/src/devkit-internals.ts b/packages/nx/src/devkit-internals.ts index acba9b54c7397..cd12d71e36086 100644 --- a/packages/nx/src/devkit-internals.ts +++ b/packages/nx/src/devkit-internals.ts @@ -13,3 +13,4 @@ export { combineOptionsForExecutor } from './utils/params'; export { sortObjectByKeys } from './utils/object-sort'; export { stripIndent } from './utils/logger'; export { readModulePackageJson } from './utils/package-json'; +export { splitByColons } from './utils/split-target';