feat(core): support targets with colons in the name without quotes (#13938)

This commit is contained in:
Craigory Coppola 2022-12-28 15:18:07 -05:00 committed by GitHub
parent a04a2ea00b
commit 285dc39371
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 275 additions and 93 deletions

View File

@ -1749,20 +1749,35 @@ Object the JSON content represents
**parseTargetString**(`targetString`): [`Target`](../../devkit/documents/index#target) **parseTargetString**(`targetString`): [`Target`](../../devkit/documents/index#target)
**`deprecated(v17)`** A project graph should be passed to parseTargetString for best accuracy.
#### Parameters
| Name | Type |
| :------------- | :------- |
| `targetString` | `string` |
#### Returns
[`Target`](../../devkit/documents/index#target)
**parseTargetString**(`targetString`, `projectGraph`): [`Target`](../../devkit/documents/index#target)
Parses a target string into {project, target, configuration} Parses a target string into {project, target, configuration}
Examples: Examples:
```typescript ```typescript
parseTargetString('proj:test'); // returns { project: "proj", target: "test" } parseTargetString('proj:test', graph); // returns { project: "proj", target: "test" }
parseTargetString('proj:test:production'); // returns { project: "proj", target: "test", configuration: "production" } parseTargetString('proj:test:production', graph); // returns { project: "proj", target: "test", configuration: "production" }
``` ```
#### Parameters #### Parameters
| Name | Type | Description | | Name | Type | Description |
| :------------- | :------- | :--------------- | | :------------- | :------------------------------------------------------------------ | :--------------- |
| `targetString` | `string` | target reference | | `targetString` | `string` | target reference |
| `projectGraph` | [`ProjectGraph`](../../devkit/documents/index#projectgraph)<`any`\> | - |
#### Returns #### Returns

File diff suppressed because one or more lines are too long

View File

@ -1749,20 +1749,35 @@ Object the JSON content represents
**parseTargetString**(`targetString`): [`Target`](../../devkit/documents/index#target) **parseTargetString**(`targetString`): [`Target`](../../devkit/documents/index#target)
**`deprecated(v17)`** A project graph should be passed to parseTargetString for best accuracy.
#### Parameters
| Name | Type |
| :------------- | :------- |
| `targetString` | `string` |
#### Returns
[`Target`](../../devkit/documents/index#target)
**parseTargetString**(`targetString`, `projectGraph`): [`Target`](../../devkit/documents/index#target)
Parses a target string into {project, target, configuration} Parses a target string into {project, target, configuration}
Examples: Examples:
```typescript ```typescript
parseTargetString('proj:test'); // returns { project: "proj", target: "test" } parseTargetString('proj:test', graph); // returns { project: "proj", target: "test" }
parseTargetString('proj:test:production'); // returns { project: "proj", target: "test", configuration: "production" } parseTargetString('proj:test:production', graph); // returns { project: "proj", target: "test", configuration: "production" }
``` ```
#### Parameters #### Parameters
| Name | Type | Description | | Name | Type | Description |
| :------------- | :------- | :--------------- | | :------------- | :------------------------------------------------------------------ | :--------------- |
| `targetString` | `string` | target reference | | `targetString` | `string` | target reference |
| `projectGraph` | [`ProjectGraph`](../../devkit/documents/index#projectgraph)<`any`\> | - |
#### Returns #### Returns

View File

@ -147,7 +147,10 @@ function getBuildableTarget(ctContext: ExecutorContext) {
); );
} }
return parseTargetString(cypressCtOptions.devServerTarget); return parseTargetString(
cypressCtOptions.devServerTarget,
ctContext.projectGraph
);
} }
function normalizeBuildTargetOptions( function normalizeBuildTargetOptions(

View File

@ -4,7 +4,11 @@ import {
executeDevServerBuilder, executeDevServerBuilder,
} from '@angular-devkit/build-angular'; } from '@angular-devkit/build-angular';
import { JsonObject } from '@angular-devkit/core'; import { JsonObject } from '@angular-devkit/core';
import { joinPathFragments, parseTargetString } from '@nrwl/devkit'; import {
joinPathFragments,
parseTargetString,
readCachedProjectGraph,
} from '@nrwl/devkit';
import { WebpackNxBuildCoordinationPlugin } from '@nrwl/webpack/src/plugins/webpack-nx-build-coordination-plugin'; import { WebpackNxBuildCoordinationPlugin } from '@nrwl/webpack/src/plugins/webpack-nx-build-coordination-plugin';
import { DependentBuildableProjectNode } from '@nrwl/workspace/src/utilities/buildable-libs-utils'; import { DependentBuildableProjectNode } from '@nrwl/workspace/src/utilities/buildable-libs-utils';
import { readCachedProjectConfiguration } from 'nx/src/project-graph/project-graph'; import { readCachedProjectConfiguration } from 'nx/src/project-graph/project-graph';
@ -26,7 +30,10 @@ export function executeWebpackDevServerBuilder(
const options = normalizeOptions(rawOptions); const options = normalizeOptions(rawOptions);
const parsedBrowserTarget = parseTargetString(options.browserTarget); const parsedBrowserTarget = parseTargetString(
options.browserTarget,
readCachedProjectGraph()
);
const browserTargetProjectConfiguration = readCachedProjectConfiguration( const browserTargetProjectConfiguration = readCachedProjectConfiguration(
parsedBrowserTarget.project parsedBrowserTarget.project
); );

View File

@ -42,7 +42,7 @@ export async function* delegateBuildExecutor(
} }
const { buildTarget, ...targetOptions } = options; const { buildTarget, ...targetOptions } = options;
const delegateTarget = parseTargetString(buildTarget); const delegateTarget = parseTargetString(buildTarget, context.projectGraph);
yield* await runExecutor(delegateTarget, targetOptions, context); yield* await runExecutor(delegateTarget, targetOptions, context);
} }

View File

@ -1,4 +1,5 @@
import { import {
createProjectGraphAsync,
formatFiles, formatFiles,
getProjects, getProjects,
parseTargetString, parseTargetString,
@ -21,14 +22,14 @@ export default async function convertWebpackBrowserBuildTargetToDelegateBuild(
); );
for (const target of webpackBrowserTargets) { for (const target of webpackBrowserTargets) {
const configurationOptions = getTargetConfigurationOptions(target); const configurationOptions = getTargetConfigurationOptions(target);
const buildTargetName = getBuildTargetNameFromOptions( const buildTargetName = await getBuildTargetNameFromOptions(
target.options, target.options,
configurationOptions configurationOptions
); );
if (buildTargetName) { if (buildTargetName) {
target.executor = '@nrwl/angular:delegate-build'; target.executor = '@nrwl/angular:delegate-build';
updateTargetsOptions(project, target, buildTargetName); updateTargetsOptions(project, target, buildTargetName);
updateTargetsConfigurations( await updateTargetsConfigurations(
project, project,
projectName, projectName,
target, target,
@ -52,14 +53,17 @@ function cleanupBuildTargetProperties(options: {
delete options.outputPath; delete options.outputPath;
} }
function extractConfigurationBuildTarget( async function extractConfigurationBuildTarget(
project: string, project: string,
target: string, target: string,
configuration: string, configuration: string,
buildTarget: string buildTarget: string
): Target { ): Promise<Target> {
if (buildTarget) { if (buildTarget) {
const buildTargetObj = parseTargetString(buildTarget); const buildTargetObj = parseTargetString(
buildTarget,
await createProjectGraphAsync()
);
return { return {
...buildTargetObj, ...buildTargetObj,
configuration: buildTargetObj.configuration ?? configuration, configuration: buildTargetObj.configuration ?? configuration,
@ -73,16 +77,17 @@ function extractConfigurationBuildTarget(
}; };
} }
function getBuildTargetNameFromOptions( async function getBuildTargetNameFromOptions(
baseOptions: any, baseOptions: any,
configurationOptions: Map<string, any> configurationOptions: Map<string, any>
): string { ): Promise<string> {
const pg = await createProjectGraphAsync();
if (baseOptions.buildTarget) { if (baseOptions.buildTarget) {
return parseTargetString(baseOptions.buildTarget).target; return parseTargetString(baseOptions.buildTarget, pg).target;
} }
for (const [, options] of configurationOptions) { for (const [, options] of configurationOptions) {
if (options.buildTarget) { if (options.buildTarget) {
return parseTargetString(options.buildTarget).target; return parseTargetString(options.buildTarget, pg).target;
} }
} }
} }
@ -102,7 +107,7 @@ function getTargetConfigurationOptions(
return targets; return targets;
} }
function updateTargetsConfigurations( async function updateTargetsConfigurations(
project: ProjectConfiguration, project: ProjectConfiguration,
projectName: string, projectName: string,
target: TargetConfiguration, target: TargetConfiguration,
@ -113,7 +118,7 @@ function updateTargetsConfigurations(
const { buildTarget, tsConfig, outputPath, ...delegateTargetOptions } = const { buildTarget, tsConfig, outputPath, ...delegateTargetOptions } =
options; options;
const configurationBuildTarget = extractConfigurationBuildTarget( const configurationBuildTarget = await extractConfigurationBuildTarget(
projectName, projectName,
buildTargetName, buildTargetName,
configurationName, configurationName,

View File

@ -2,6 +2,7 @@ import { CypressExecutorOptions } from '../../executors/cypress/cypress.impl';
import { CY_FILE_MATCHER } from '../../utils/ct-helpers'; import { CY_FILE_MATCHER } from '../../utils/ct-helpers';
import { installedCypressVersion } from '../../utils/cypress-version'; import { installedCypressVersion } from '../../utils/cypress-version';
import { import {
createProjectGraphAsync,
formatFiles, formatFiles,
getProjects, getProjects,
joinPathFragments, joinPathFragments,
@ -23,6 +24,7 @@ export async function updateCyMountUsage(tree: Tree) {
} }
const projects = getProjects(tree); const projects = getProjects(tree);
const graph = await createProjectGraphAsync();
forEachExecutorOptions<CypressExecutorOptions>( forEachExecutorOptions<CypressExecutorOptions>(
tree, tree,
@ -32,7 +34,7 @@ export async function updateCyMountUsage(tree: Tree) {
return; return;
} }
const parsed = parseTargetString(options.devServerTarget); const parsed = parseTargetString(options.devServerTarget, graph);
if (!parsed?.project || !parsed?.target) { if (!parsed?.project || !parsed?.target) {
return; return;
} }

View File

@ -26,7 +26,10 @@ export default async function* detoxTestExecutor(
try { try {
if (options.buildTarget) { if (options.buildTarget) {
const buildTarget = parseTargetString(options.buildTarget); const buildTarget = parseTargetString(
options.buildTarget,
context.projectGraph
);
const buildOptions = readTargetOptions<DetoxBuildOptions>( const buildOptions = readTargetOptions<DetoxBuildOptions>(
buildTarget, buildTarget,
context context

View File

@ -162,9 +162,6 @@ export { updateTsConfigsToJs } from './src/generators/update-ts-configs-to-js';
*/ */
export { visitNotIgnoredFiles } from './src/generators/visit-not-ignored-files'; export { visitNotIgnoredFiles } from './src/generators/visit-not-ignored-files';
/**
* @category Executors
*/
export { export {
parseTargetString, parseTargetString,
targetToTargetString, targetToTargetString,

View File

@ -1,5 +1,7 @@
import { parseTargetString, targetToTargetString } from './parse-target-string'; import { parseTargetString, targetToTargetString } from './parse-target-string';
import * as splitTarget from 'nx/src/utils/split-target';
const cases = [ const cases = [
{ input: 'one:two', expected: { project: 'one', target: 'two' } }, { input: 'one:two', expected: { project: 'one', target: 'two' } },
{ {
@ -14,7 +16,10 @@ const cases = [
describe('parseTargetString', () => { describe('parseTargetString', () => {
it.each(cases)('$input -> $expected', ({ input, expected }) => { it.each(cases)('$input -> $expected', ({ input, expected }) => {
expect(parseTargetString(input)).toEqual(expected); jest
.spyOn(splitTarget, 'splitTarget')
.mockReturnValueOnce(Object.values(expected) as [string]);
expect(parseTargetString(input, null)).toEqual(expected);
}); });
}); });

View File

@ -1,19 +1,35 @@
import type { Target } from 'nx/src/command-line/run'; import type { Target } from 'nx/src/command-line/run';
import { ProjectGraph } from 'nx/src/config/project-graph';
import { readCachedProjectGraph } from 'nx/src/project-graph/project-graph';
import { splitTarget } from 'nx/src/utils/split-target'; import { splitTarget } from 'nx/src/utils/split-target';
/**
* @deprecated(v17) A project graph should be passed to parseTargetString for best accuracy.
*/
export function parseTargetString(targetString: string): Target;
/** /**
* Parses a target string into {project, target, configuration} * Parses a target string into {project, target, configuration}
* *
* Examples: * Examples:
* ```typescript * ```typescript
* parseTargetString("proj:test") // returns { project: "proj", target: "test" } * parseTargetString("proj:test", graph) // returns { project: "proj", target: "test" }
* parseTargetString("proj:test:production") // returns { project: "proj", target: "test", configuration: "production" } * parseTargetString("proj:test:production", graph) // returns { project: "proj", target: "test", configuration: "production" }
* ``` * ```
* *
* @param targetString - target reference * @param targetString - target reference
*/ */
export function parseTargetString(targetString: string): Target { export function parseTargetString(
const [project, target, configuration] = splitTarget(targetString); targetString: string,
projectGraph: ProjectGraph
): Target;
export function parseTargetString(
targetString: string,
projectGraph = readCachedProjectGraph()
): Target {
const [project, target, configuration] = splitTarget(
targetString,
projectGraph
);
if (!project || !target) { if (!project || !target) {
throw new Error(`Invalid Target String: ${targetString}`); throw new Error(`Invalid Target String: ${targetString}`);
} }

View File

@ -66,7 +66,7 @@ function calculateResolveMappings(
context: ExecutorContext, context: ExecutorContext,
options: NodeExecutorOptions options: NodeExecutorOptions
) { ) {
const parsed = parseTargetString(options.buildTarget); const parsed = parseTargetString(options.buildTarget, context.projectGraph);
const { dependencies } = calculateProjectDependencies( const { dependencies } = calculateProjectDependencies(
context.projectGraph, context.projectGraph,
context.root, context.root,
@ -198,7 +198,10 @@ async function* startBuild(
options: NodeExecutorOptions, options: NodeExecutorOptions,
context: ExecutorContext context: ExecutorContext
) { ) {
const buildTarget = parseTargetString(options.buildTarget); const buildTarget = parseTargetString(
options.buildTarget,
context.projectGraph
);
yield* await runExecutor<ExecutorEvent>( yield* await runExecutor<ExecutorEvent>(
buildTarget, buildTarget,
@ -216,7 +219,7 @@ function runWaitUntilTargets(
): Promise<{ success: boolean }[]> { ): Promise<{ success: boolean }[]> {
return Promise.all( return Promise.all(
options.waitUntilTargets.map(async (waitUntilTarget) => { options.waitUntilTargets.map(async (waitUntilTarget) => {
const target = parseTargetString(waitUntilTarget); const target = parseTargetString(waitUntilTarget, context.projectGraph);
const output = await runExecutor(target, {}, context); const output = await runExecutor(target, {}, context);
return new Promise<{ success: boolean }>(async (resolve) => { return new Promise<{ success: boolean }>(async (resolve) => {
let event = await output.next(); let event = await output.next();

View File

@ -44,7 +44,7 @@ export default async function* serveExecutor(
(process.env as any).PORT = options.port; (process.env as any).PORT = options.port;
const buildOptions = readTargetOptions<NextBuildBuilderOptions>( const buildOptions = readTargetOptions<NextBuildBuilderOptions>(
parseTargetString(options.buildTarget), parseTargetString(options.buildTarget, context.projectGraph),
context context
); );
const root = resolve(context.root, buildOptions.root); const root = resolve(context.root, buildOptions.root);
@ -155,7 +155,7 @@ async function* runCustomServer(
const baseUrl = `http://${options.hostname || 'localhost'}:${options.port}`; const baseUrl = `http://${options.hostname || 'localhost'}:${options.port}`;
const customServerBuild = await runExecutor( const customServerBuild = await runExecutor(
parseTargetString(options.customServerTarget), parseTargetString(options.customServerTarget, context.projectGraph),
{ {
watch: true, watch: true,
}, },

View File

@ -12,6 +12,7 @@ import { workspaceRoot } from '../utils/workspace-root';
import { splitTarget } from '../utils/split-target'; import { splitTarget } from '../utils/split-target';
import { output } from '../utils/output'; import { output } from '../utils/output';
import { import {
ProjectConfiguration,
ProjectsConfigurations, ProjectsConfigurations,
TargetDependencyConfig, TargetDependencyConfig,
} from '../config/workspace-json-project-json'; } from '../config/workspace-json-project-json';
@ -35,10 +36,7 @@ export async function runOne(
const nxJson = readNxJson(); const nxJson = readNxJson();
const projectGraph = await createProjectGraphAsync({ exitOnError: true }); const projectGraph = await createProjectGraphAsync({ exitOnError: true });
const opts = parseRunOneOptions(cwd, args, { const opts = parseRunOneOptions(cwd, args, projectGraph, nxJson);
...readProjectsConfigurationFromProjectGraph(projectGraph),
...nxJson,
});
const { nxArgs, overrides } = splitArgsIntoNxArgsAndOverrides( const { nxArgs, overrides } = splitArgsIntoNxArgsAndOverrides(
{ {
@ -102,13 +100,13 @@ const targetAliases = {
function parseRunOneOptions( function parseRunOneOptions(
cwd: string, cwd: string,
parsedArgs: { [k: string]: any }, parsedArgs: { [k: string]: any },
workspaceConfiguration: ProjectsConfigurations & NxJsonConfiguration projectGraph: ProjectGraph<ProjectConfiguration>,
nxJson: NxJsonConfiguration
): { project; target; configuration; parsedArgs } { ): { project; target; configuration; parsedArgs } {
const defaultProjectName = calculateDefaultProjectName( const defaultProjectName = calculateDefaultProjectName(cwd, workspaceRoot, {
cwd, ...readProjectsConfigurationFromProjectGraph(projectGraph),
workspaceRoot, ...nxJson,
workspaceConfiguration });
);
let project; let project;
let target; let target;
@ -117,7 +115,8 @@ function parseRunOneOptions(
if (parsedArgs['project:target:configuration'].indexOf(':') > -1) { if (parsedArgs['project:target:configuration'].indexOf(':') > -1) {
// run case // run case
[project, target, configuration] = splitTarget( [project, target, configuration] = splitTarget(
parsedArgs['project:target:configuration'] parsedArgs['project:target:configuration'],
projectGraph
); );
// this is to account for "nx npmsript:dev" // this is to account for "nx npmsript:dev"
if (project && !target && defaultProjectName) { if (project && !target && defaultProjectName) {

View File

@ -1,14 +1,56 @@
import { ProjectGraph } from '../config/project-graph';
import { ProjectGraphBuilder } from '../project-graph/project-graph-builder';
import { splitTarget } from './split-target'; import { splitTarget } from './split-target';
const cases = [ let projectGraph: ProjectGraph;
{ input: 'one', expected: ['one'] },
{ input: 'one:two', expected: ['one', 'two'] },
{ input: 'one:two:three', expected: ['one', 'two', 'three'] },
{ input: 'one:"two:two":three', expected: ['one', 'two:two', 'three'] },
];
describe('splitTarget', () => { describe('splitTarget', () => {
it.each(cases)('$input -> $expected', ({ input, expected }) => { beforeAll(() => {
expect(splitTarget(input)).toEqual(expected); let builder = new ProjectGraphBuilder();
builder.addNode({
name: 'project',
data: {
files: [],
root: '',
targets: {
target: {},
'target:target': {},
},
},
type: 'app',
});
projectGraph = builder.getUpdatedProjectGraph();
});
it('should support only project', () => {
expect(splitTarget('project', projectGraph)).toEqual(['project']);
});
it('should project:target', () => {
expect(splitTarget('project:target', projectGraph)).toEqual([
'project',
'target',
]);
});
it('should project:target:configuration', () => {
expect(splitTarget('project:target:configuration', projectGraph)).toEqual([
'project',
'target',
'configuration',
]);
});
it('should targets that contain colons when present in the graph', () => {
expect(
splitTarget('project:target:target:configuration', projectGraph)
).toEqual(['project', 'target:target', 'configuration']);
});
it('should targets that contain colons when not present in the graph but surrounded by quotes', () => {
expect(
splitTarget('project:"other:other":configuration', projectGraph)
).toEqual(['project', 'other:other', 'configuration']);
}); });
}); });

View File

@ -1,6 +1,43 @@
import { ProjectGraph } from '../config/project-graph';
import { ProjectConfiguration } from '../config/workspace-json-project-json';
export function splitTarget( export function splitTarget(
s: string s: string,
projectGraph: ProjectGraph<ProjectConfiguration>
): [project: string, target?: string, configuration?: string] { ): [project: string, target?: string, configuration?: string] {
let [project, ...segments] = splitByColons(s);
const validTargets = projectGraph.nodes[project]?.data?.targets;
const validTargetNames = new Set(Object.keys(validTargets ?? {}));
return [project, ...groupJointSegments(segments, validTargetNames)] as [
string,
string?,
string?
];
}
function groupJointSegments(segments: string[], validTargetNames: Set<string>) {
for (
let endingSegmentIdx = segments.length;
endingSegmentIdx > 0;
endingSegmentIdx--
) {
const potentialTargetName = segments.slice(0, endingSegmentIdx).join(':');
if (validTargetNames.has(potentialTargetName)) {
const configurationName =
endingSegmentIdx < segments.length
? segments.slice(endingSegmentIdx).join(':')
: null;
return configurationName
? [potentialTargetName, configurationName]
: [potentialTargetName];
}
}
// If we can't find a segment match, keep older behaviour
return segments;
}
function splitByColons(s: string) {
const parts = [] as string[]; const parts = [] as string[];
let currentPart = ''; let currentPart = '';
for (let i = 0; i < s.length; ++i) { for (let i = 0; i < s.length; ++i) {
@ -17,5 +54,5 @@ export function splitTarget(
} }
} }
parts.push(currentPart); parts.push(currentPart);
return parts as [string, string?, string?]; return parts as [string, ...string[]];
} }

View File

@ -166,7 +166,7 @@ function buildTargetWebpack(
buildTarget: string, buildTarget: string,
componentTestingProjectName: string componentTestingProjectName: string
) { ) {
const parsed = parseTargetString(buildTarget); const parsed = parseTargetString(buildTarget, graph);
const buildableProjectConfig = graph.nodes[parsed.project]?.data; const buildableProjectConfig = graph.nodes[parsed.project]?.data;
const ctProjectConfig = graph.nodes[componentTestingProjectName]?.data; const ctProjectConfig = graph.nodes[componentTestingProjectName]?.data;

View File

@ -1,10 +1,12 @@
import { import {
createProjectGraphAsync,
ensurePackage, ensurePackage,
generateFiles, generateFiles,
joinPathFragments, joinPathFragments,
logger, logger,
parseTargetString, parseTargetString,
ProjectConfiguration, ProjectConfiguration,
readCachedProjectGraph,
readProjectConfiguration, readProjectConfiguration,
Tree, Tree,
visitNotIgnoredFiles, visitNotIgnoredFiles,
@ -33,7 +35,7 @@ export async function addFiles(
tree.delete(cypressConfigPath); tree.delete(cypressConfigPath);
} }
const actualBundler = getBundler(found, tree); const actualBundler = await getBundler(found, tree);
if (options.bundler && options.bundler !== actualBundler) { if (options.bundler && options.bundler !== actualBundler) {
logger.warn( logger.warn(
@ -76,12 +78,18 @@ export async function addFiles(
} }
} }
function getBundler(found: FoundTarget, tree: Tree): 'vite' | 'webpack' { async function getBundler(
found: FoundTarget,
tree: Tree
): Promise<'vite' | 'webpack'> {
if (found.target && found.config?.executor) { if (found.target && found.config?.executor) {
return found.config.executor === '@nrwl/vite:build' ? 'vite' : 'webpack'; return found.config.executor === '@nrwl/vite:build' ? 'vite' : 'webpack';
} }
const { target, project } = parseTargetString(found.target); const { target, project } = parseTargetString(
found.target,
await createProjectGraphAsync()
);
const projectConfig = readProjectConfiguration(tree, project); const projectConfig = readProjectConfiguration(tree, project);
return projectConfig?.targets?.[target]?.executor === '@nrwl/vite:build' return projectConfig?.targets?.[target]?.executor === '@nrwl/vite:build'
? 'vite' ? 'vite'

View File

@ -107,7 +107,7 @@ export async function configurationGenerator(
); );
} }
const e2eProject = getE2EProjectName(tree, schema.name); const e2eProject = await getE2EProjectName(tree, schema.name);
if (schema.configureCypress && !e2eProject) { if (schema.configureCypress && !e2eProject) {
const cypressTask = await cypressProjectGenerator(tree, { const cypressTask = await cypressProjectGenerator(tree, {
name: schema.name, name: schema.name,

View File

@ -1,6 +1,6 @@
import { import {
createProjectGraphAsync,
generateFiles, generateFiles,
getProjects,
joinPathFragments, joinPathFragments,
logger, logger,
offsetFromRoot, offsetFromRoot,
@ -537,11 +537,12 @@ export function rootFileIsTs(
} }
} }
export function getE2EProjectName( export async function getE2EProjectName(
tree: Tree, tree: Tree,
mainProject: string mainProject: string
): string | undefined { ): Promise<string | undefined> {
let e2eProject: string; let e2eProject: string;
const graph = await createProjectGraphAsync();
forEachExecutorOptions( forEachExecutorOptions(
tree, tree,
'@nrwl/cypress:cypress', '@nrwl/cypress:cypress',
@ -551,7 +552,8 @@ export function getE2EProjectName(
} }
if (options['devServerTarget']) { if (options['devServerTarget']) {
const { project, target } = parseTargetString( const { project, target } = parseTargetString(
options['devServerTarget'] options['devServerTarget'],
graph
); );
if ( if (
(project === mainProject && target === 'serve') || (project === mainProject && target === 'serve') ||

View File

@ -97,7 +97,7 @@ export function getBuildTargetOptions(
buildTarget: string, buildTarget: string,
context: ExecutorContext context: ExecutorContext
) { ) {
const target = parseTargetString(buildTarget); const target = parseTargetString(buildTarget, context.projectGraph);
return readTargetOptions(target, context); return readTargetOptions(target, context);
} }

View File

@ -65,7 +65,7 @@ function getBuildTargetOutputPath(options: Schema, context: ExecutorContext) {
let buildOptions; let buildOptions;
try { try {
const target = parseTargetString(options.buildTarget); const target = parseTargetString(options.buildTarget, context.projectGraph);
buildOptions = readTargetOptions(target, context); buildOptions = readTargetOptions(target, context);
} catch (e) { } catch (e) {
throw new Error(`Invalid buildTarget: ${options.buildTarget}`); throw new Error(`Invalid buildTarget: ${options.buildTarget}`);

View File

@ -93,7 +93,7 @@ function getBuildOptions(
options: WebDevServerOptions, options: WebDevServerOptions,
context: ExecutorContext context: ExecutorContext
): WebpackExecutorOptions { ): WebpackExecutorOptions {
const target = parseTargetString(options.buildTarget); const target = parseTargetString(options.buildTarget, context.projectGraph);
const overrides: Partial<WebpackExecutorOptions> = { const overrides: Partial<WebpackExecutorOptions> = {
watch: false, watch: false,

View File

@ -15,8 +15,14 @@ export async function* ssrDevServerExecutor(
options: WebSsrDevServerOptions, options: WebSsrDevServerOptions,
context: ExecutorContext context: ExecutorContext
) { ) {
const browserTarget = parseTargetString(options.browserTarget); const browserTarget = parseTargetString(
const serverTarget = parseTargetString(options.serverTarget); options.browserTarget,
context.projectGraph
);
const serverTarget = parseTargetString(
options.serverTarget,
context.projectGraph
);
const browserOptions = readTargetOptions<WebpackExecutorOptions>( const browserOptions = readTargetOptions<WebpackExecutorOptions>(
browserTarget, browserTarget,
context context

View File

@ -65,9 +65,8 @@ describe('checkTargets', () => {
}); });
it('should throw an error if another project targets', async () => { it('should throw an error if another project targets', async () => {
expect(() => { await expect(checkTargets(tree, schema)).rejects
checkTargets(tree, schema); .toThrowErrorMatchingInlineSnapshot(`
}).toThrowErrorMatchingInlineSnapshot(`
"ng-app is still targeted by some projects: "ng-app is still targeted by some projects:
\\"ng-app:serve\\" is used by \\"ng-app-e2e\\" \\"ng-app:serve\\" is used by \\"ng-app-e2e\\"
@ -78,25 +77,19 @@ describe('checkTargets', () => {
it('should NOT throw an error if no other project targets', async () => { it('should NOT throw an error if no other project targets', async () => {
schema.projectName = 'ng-app-e2e'; schema.projectName = 'ng-app-e2e';
expect(() => { await expect(checkTargets(tree, schema)).resolves.toBeUndefined();
checkTargets(tree, schema);
}).not.toThrow();
}); });
it('should NOT throw an error if it is a nrwl package', async () => { it('should NOT throw an error if it is a nrwl package', async () => {
schema.projectName = 'storybook'; schema.projectName = 'storybook';
expect(() => { await expect(checkTargets(tree, schema)).resolves.toBeUndefined();
checkTargets(tree, schema);
}).not.toThrow();
}); });
it('should not error if forceRemove is true', async () => { it('should not error if forceRemove is true', async () => {
schema.forceRemove = true; schema.forceRemove = true;
expect(() => { await expect(checkTargets(tree, schema)).resolves.toBeUndefined();
checkTargets(tree, schema);
}).not.toThrow();
}); });
describe('use project in other project target', () => { describe('use project in other project target', () => {
@ -122,9 +115,7 @@ describe('checkTargets', () => {
it('should throw an error since it is used as a target in another project', async () => { it('should throw an error since it is used as a target in another project', async () => {
schema.projectName = 'storybook'; schema.projectName = 'storybook';
expect(() => { await expect(checkTargets(tree, schema)).rejects.toThrow();
checkTargets(tree, schema);
}).toThrow();
}); });
}); });
}); });

View File

@ -1,4 +1,5 @@
import { import {
createProjectGraphAsync,
getProjects, getProjects,
parseTargetString, parseTargetString,
TargetConfiguration, TargetConfiguration,
@ -13,11 +14,12 @@ import { Schema } from '../schema';
* *
* @param schema The options provided to the schematic * @param schema The options provided to the schematic
*/ */
export function checkTargets(tree: Tree, schema: Schema) { export async function checkTargets(tree: Tree, schema: Schema) {
if (schema.forceRemove) { if (schema.forceRemove) {
return; return;
} }
const graph = await createProjectGraphAsync();
const errors: string[] = []; const errors: string[] = [];
getProjects(tree).forEach((projectConfig, projectName) => { getProjects(tree).forEach((projectConfig, projectName) => {
@ -27,7 +29,7 @@ export function checkTargets(tree: Tree, schema: Schema) {
Object.entries(projectConfig.targets || {}).forEach(([, targetConfig]) => { Object.entries(projectConfig.targets || {}).forEach(([, targetConfig]) => {
checkIfProjectIsUsed(targetConfig, (value) => { checkIfProjectIsUsed(targetConfig, (value) => {
try { try {
const { project } = parseTargetString(value); const { project } = parseTargetString(value, graph);
if (project === schema.projectName) { if (project === schema.projectName) {
errors.push(`"${value}" is used by "${projectName}"`); errors.push(`"${value}" is used by "${projectName}"`);
} }

View File

@ -18,7 +18,7 @@ import { updateJestConfig } from './lib/update-jest-config';
export async function removeGenerator(tree: Tree, schema: Schema) { export async function removeGenerator(tree: Tree, schema: Schema) {
const project = readProjectConfiguration(tree, schema.projectName); const project = readProjectConfiguration(tree, schema.projectName);
await checkDependencies(tree, schema); await checkDependencies(tree, schema);
checkTargets(tree, schema); await checkTargets(tree, schema);
updateJestConfig(tree, schema, project); updateJestConfig(tree, schema, project);
removeProjectConfig(tree, schema); removeProjectConfig(tree, schema);
removeProject(tree, project); removeProject(tree, project);