feat(core): add an option to seperate the output of show with provide… (#23172)
Co-authored-by: Craigory Coppola <craigorycoppola@gmail.com>
This commit is contained in:
parent
5edc64af92
commit
d9a97120f6
@ -145,6 +145,12 @@ Type: `string`
|
||||
|
||||
Show only projects that match a given pattern.
|
||||
|
||||
##### sep
|
||||
|
||||
Type: `string`
|
||||
|
||||
Outputs projects with the specified seperator
|
||||
|
||||
##### type
|
||||
|
||||
Type: `string`
|
||||
|
||||
@ -145,6 +145,12 @@ Type: `string`
|
||||
|
||||
Show only projects that match a given pattern.
|
||||
|
||||
##### sep
|
||||
|
||||
Type: `string`
|
||||
|
||||
Outputs projects with the specified seperator
|
||||
|
||||
##### type
|
||||
|
||||
Type: `string`
|
||||
|
||||
@ -12,17 +12,18 @@ export interface NxShowArgs {
|
||||
}
|
||||
|
||||
export type ShowProjectsOptions = NxShowArgs & {
|
||||
exclude: string;
|
||||
files: string;
|
||||
uncommitted: any;
|
||||
untracked: any;
|
||||
base: string;
|
||||
head: string;
|
||||
affected: boolean;
|
||||
type: ProjectGraphProjectNode['type'];
|
||||
projects: string[];
|
||||
withTarget: string[];
|
||||
verbose: boolean;
|
||||
exclude?: string[];
|
||||
files?: string;
|
||||
uncommitted?: any;
|
||||
untracked?: any;
|
||||
base?: string;
|
||||
head?: string;
|
||||
affected?: boolean;
|
||||
type?: ProjectGraphProjectNode['type'];
|
||||
projects?: string[];
|
||||
withTarget?: string[];
|
||||
verbose?: boolean;
|
||||
sep?: string;
|
||||
};
|
||||
|
||||
export type ShowProjectOptions = NxShowArgs & {
|
||||
@ -90,11 +91,17 @@ const showProjectsCommand: CommandModule<NxShowArgs, ShowProjectsOptions> = {
|
||||
description: 'Select only projects of the given type',
|
||||
choices: ['app', 'lib', 'e2e'],
|
||||
})
|
||||
.option('sep', {
|
||||
type: 'string',
|
||||
description: 'Outputs projects with the specified seperator',
|
||||
})
|
||||
.implies('untracked', 'affected')
|
||||
.implies('uncommitted', 'affected')
|
||||
.implies('files', 'affected')
|
||||
.implies('base', 'affected')
|
||||
.implies('head', 'affected')
|
||||
.conflicts('sep', 'json')
|
||||
.conflicts('json', 'sep')
|
||||
.example(
|
||||
'$0 show projects --projects "apps/*"',
|
||||
'Show all projects in the apps directory'
|
||||
@ -119,7 +126,9 @@ const showProjectsCommand: CommandModule<NxShowArgs, ShowProjectsOptions> = {
|
||||
return handleErrors(
|
||||
args.verbose ?? process.env.NX_VERBOSE_LOGGING === 'true',
|
||||
async () => {
|
||||
return (await import('./show')).showProjectsHandler(args);
|
||||
const { showProjectsHandler } = await import('./projects');
|
||||
await showProjectsHandler(args);
|
||||
process.exit(0);
|
||||
}
|
||||
);
|
||||
},
|
||||
@ -145,21 +154,23 @@ const showProjectCommand: CommandModule<NxShowArgs, ShowProjectOptions> = {
|
||||
description:
|
||||
'Prints additional information about the commands (e.g., stack traces)',
|
||||
})
|
||||
.check((argv) => {
|
||||
if (argv.web) {
|
||||
argv.json = false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.conflicts('json', 'web')
|
||||
.conflicts('web', 'json')
|
||||
.example(
|
||||
'$0 show project my-app',
|
||||
'View project information for my-app in JSON format'
|
||||
)
|
||||
.example(
|
||||
'$0 show project my-app --web',
|
||||
'View project information for my-app in the browser'
|
||||
),
|
||||
handler: (args) => {
|
||||
return handleErrors(
|
||||
args.verbose ?? process.env.NX_VERBOSE_LOGGING === 'true',
|
||||
async () => {
|
||||
return (await import('./show')).showProjectHandler(args);
|
||||
const { showProjectHandler } = await import('./project');
|
||||
await showProjectHandler(args);
|
||||
process.exit(0);
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
67
packages/nx/src/command-line/show/project.ts
Normal file
67
packages/nx/src/command-line/show/project.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import { output } from '../../utils/output';
|
||||
import { createProjectGraphAsync } from '../../project-graph/project-graph';
|
||||
import { ShowProjectOptions } from './command-object';
|
||||
import { generateGraph } from '../graph/graph';
|
||||
|
||||
export async function showProjectHandler(
|
||||
args: ShowProjectOptions
|
||||
): Promise<void> {
|
||||
const graph = await createProjectGraphAsync();
|
||||
const node = graph.nodes[args.projectName];
|
||||
if (!node) {
|
||||
console.log(`Could not find project ${args.projectName}`);
|
||||
process.exit(1);
|
||||
}
|
||||
if (args.json) {
|
||||
console.log(JSON.stringify(node.data));
|
||||
} else if (args.web) {
|
||||
await generateGraph(
|
||||
{
|
||||
view: 'project-details',
|
||||
focus: node.name,
|
||||
watch: true,
|
||||
open: true,
|
||||
},
|
||||
[]
|
||||
);
|
||||
} else {
|
||||
const chalk = require('chalk') as typeof import('chalk');
|
||||
const logIfExists = (label, key: keyof typeof node['data']) => {
|
||||
if (node.data[key]) {
|
||||
console.log(`${chalk.bold(label)}: ${node.data[key]}`);
|
||||
}
|
||||
};
|
||||
|
||||
logIfExists('Name', 'name');
|
||||
logIfExists('Root', 'root');
|
||||
logIfExists('Source Root', 'sourceRoot');
|
||||
logIfExists('Tags', 'tags');
|
||||
logIfExists('Implicit Dependencies', 'implicitDependencies');
|
||||
|
||||
const targets = Object.entries(node.data.targets ?? {});
|
||||
const maxTargetNameLength = Math.max(...targets.map(([t]) => t.length));
|
||||
const maxExecutorNameLength = Math.max(
|
||||
...targets.map(([, t]) => t?.executor?.length ?? 0)
|
||||
);
|
||||
|
||||
if (targets.length > 0) {
|
||||
console.log(`${chalk.bold('Targets')}: `);
|
||||
for (const [target, targetConfig] of targets) {
|
||||
console.log(
|
||||
`- ${chalk.bold((target + ':').padEnd(maxTargetNameLength + 2))} ${(
|
||||
targetConfig?.executor ?? ''
|
||||
).padEnd(maxExecutorNameLength + 2)} ${(() => {
|
||||
const configurations = Object.keys(
|
||||
targetConfig.configurations ?? {}
|
||||
);
|
||||
if (configurations.length) {
|
||||
return chalk.dim(configurations.join(', '));
|
||||
}
|
||||
return '';
|
||||
})()}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
await output.drain();
|
||||
}
|
||||
371
packages/nx/src/command-line/show/projects.spec.ts
Normal file
371
packages/nx/src/command-line/show/projects.spec.ts
Normal file
@ -0,0 +1,371 @@
|
||||
import type {
|
||||
ProjectGraph,
|
||||
ProjectGraphProjectNode,
|
||||
} from '../../config/project-graph';
|
||||
import type { ProjectConfiguration } from '../../config/workspace-json-project-json';
|
||||
import { showProjectsHandler } from './projects';
|
||||
|
||||
let graph: ProjectGraph = {
|
||||
nodes: {},
|
||||
dependencies: {},
|
||||
externalNodes: {},
|
||||
};
|
||||
|
||||
jest.mock('../../project-graph/project-graph', () => ({
|
||||
...(jest.requireActual(
|
||||
'../../project-graph/project-graph'
|
||||
) as typeof import('../../project-graph/project-graph')),
|
||||
createProjectGraphAsync: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve(graph)),
|
||||
}));
|
||||
|
||||
describe('show projects', () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(console, 'log').mockImplementation(() => {});
|
||||
});
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should print out projects with provided seperator value', async () => {
|
||||
graph = new GraphBuilder()
|
||||
.addProjectConfiguration(
|
||||
{
|
||||
root: 'proj1',
|
||||
name: 'proj1',
|
||||
},
|
||||
'app'
|
||||
)
|
||||
.addProjectConfiguration(
|
||||
{
|
||||
root: 'proj2',
|
||||
name: 'proj2',
|
||||
},
|
||||
'lib'
|
||||
)
|
||||
.addProjectConfiguration(
|
||||
{
|
||||
root: 'proj3',
|
||||
name: 'proj3',
|
||||
},
|
||||
'lib'
|
||||
)
|
||||
.build();
|
||||
|
||||
await showProjectsHandler({
|
||||
sep: ',',
|
||||
});
|
||||
|
||||
expect(console.log).toHaveBeenCalledWith('proj1,proj2,proj3');
|
||||
});
|
||||
|
||||
it('should default to printing one project per line', async () => {
|
||||
graph = new GraphBuilder()
|
||||
.addProjectConfiguration(
|
||||
{
|
||||
root: 'proj1',
|
||||
name: 'proj1',
|
||||
},
|
||||
'app'
|
||||
)
|
||||
.addProjectConfiguration(
|
||||
{
|
||||
root: 'proj2',
|
||||
name: 'proj2',
|
||||
},
|
||||
'lib'
|
||||
)
|
||||
.addProjectConfiguration(
|
||||
{
|
||||
root: 'proj3',
|
||||
name: 'proj3',
|
||||
},
|
||||
'lib'
|
||||
)
|
||||
.build();
|
||||
|
||||
await showProjectsHandler({});
|
||||
|
||||
expect(console.log).toHaveBeenCalledWith('proj1');
|
||||
expect(console.log).toHaveBeenCalledWith('proj2');
|
||||
expect(console.log).toHaveBeenCalledWith('proj3');
|
||||
expect(console.log).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it('should print out projects in json format', async () => {
|
||||
graph = new GraphBuilder()
|
||||
.addProjectConfiguration(
|
||||
{
|
||||
root: 'proj1',
|
||||
name: 'proj1',
|
||||
},
|
||||
'app'
|
||||
)
|
||||
.addProjectConfiguration(
|
||||
{
|
||||
root: 'proj2',
|
||||
name: 'proj2',
|
||||
},
|
||||
'lib'
|
||||
)
|
||||
.addProjectConfiguration(
|
||||
{
|
||||
root: 'proj3',
|
||||
name: 'proj3',
|
||||
},
|
||||
'lib'
|
||||
)
|
||||
.build();
|
||||
|
||||
await showProjectsHandler({
|
||||
json: true,
|
||||
});
|
||||
|
||||
expect(console.log).toHaveBeenCalledWith('["proj1","proj2","proj3"]');
|
||||
});
|
||||
|
||||
it('should filter projects by type', async () => {
|
||||
graph = new GraphBuilder()
|
||||
.addProjectConfiguration(
|
||||
{
|
||||
root: 'proj1',
|
||||
name: 'proj1',
|
||||
},
|
||||
'app'
|
||||
)
|
||||
.addProjectConfiguration(
|
||||
{
|
||||
root: 'proj2',
|
||||
name: 'proj2',
|
||||
},
|
||||
'lib'
|
||||
)
|
||||
.addProjectConfiguration(
|
||||
{
|
||||
root: 'proj3',
|
||||
name: 'proj3',
|
||||
},
|
||||
'lib'
|
||||
)
|
||||
.build();
|
||||
|
||||
await showProjectsHandler({
|
||||
type: 'lib',
|
||||
});
|
||||
|
||||
expect(console.log).toHaveBeenCalledWith('proj2');
|
||||
expect(console.log).toHaveBeenCalledWith('proj3');
|
||||
expect(console.log).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should filter projects by name', async () => {
|
||||
graph = new GraphBuilder()
|
||||
.addProjectConfiguration(
|
||||
{
|
||||
root: 'proj1',
|
||||
name: 'proj1',
|
||||
},
|
||||
'app'
|
||||
)
|
||||
.addProjectConfiguration(
|
||||
{
|
||||
root: 'proj2',
|
||||
name: 'proj2',
|
||||
},
|
||||
'lib'
|
||||
)
|
||||
.addProjectConfiguration(
|
||||
{
|
||||
root: 'proj3',
|
||||
name: 'proj3',
|
||||
},
|
||||
'lib'
|
||||
)
|
||||
.build();
|
||||
|
||||
await showProjectsHandler({
|
||||
projects: ['proj1', 'proj3'],
|
||||
});
|
||||
|
||||
expect(console.log).toHaveBeenCalledWith('proj1');
|
||||
expect(console.log).toHaveBeenCalledWith('proj3');
|
||||
expect(console.log).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should exclude projects by name', async () => {
|
||||
graph = new GraphBuilder()
|
||||
.addProjectConfiguration(
|
||||
{
|
||||
root: 'proj1',
|
||||
name: 'proj1',
|
||||
},
|
||||
'app'
|
||||
)
|
||||
.addProjectConfiguration(
|
||||
{
|
||||
root: 'proj2',
|
||||
name: 'proj2',
|
||||
},
|
||||
'lib'
|
||||
)
|
||||
.addProjectConfiguration(
|
||||
{
|
||||
root: 'proj3',
|
||||
name: 'proj3',
|
||||
},
|
||||
'lib'
|
||||
)
|
||||
.build();
|
||||
|
||||
await showProjectsHandler({
|
||||
exclude: ['proj1', 'proj3'],
|
||||
});
|
||||
|
||||
expect(console.log).toHaveBeenCalledWith('proj2');
|
||||
expect(console.log).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should find projects with wildcard', async () => {
|
||||
graph = new GraphBuilder()
|
||||
.addProjectConfiguration(
|
||||
{
|
||||
root: 'proj1',
|
||||
name: 'proj1',
|
||||
},
|
||||
'app'
|
||||
)
|
||||
.addProjectConfiguration(
|
||||
{
|
||||
root: 'proj2',
|
||||
name: 'proj2',
|
||||
},
|
||||
'lib'
|
||||
)
|
||||
.addProjectConfiguration(
|
||||
{
|
||||
root: 'proj3',
|
||||
name: 'proj3',
|
||||
},
|
||||
'lib'
|
||||
)
|
||||
.build();
|
||||
|
||||
await showProjectsHandler({
|
||||
projects: ['*1'],
|
||||
});
|
||||
|
||||
expect(console.log).toHaveBeenCalledWith('proj1');
|
||||
expect(console.log).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should find projects with specific tag', async () => {
|
||||
graph = new GraphBuilder()
|
||||
.addProjectConfiguration(
|
||||
{
|
||||
root: 'proj1',
|
||||
name: 'proj1',
|
||||
tags: ['tag1'],
|
||||
},
|
||||
'app'
|
||||
)
|
||||
.addProjectConfiguration(
|
||||
{
|
||||
root: 'proj2',
|
||||
name: 'proj2',
|
||||
tags: ['tag2'],
|
||||
},
|
||||
'lib'
|
||||
)
|
||||
.addProjectConfiguration(
|
||||
{
|
||||
root: 'proj3',
|
||||
name: 'proj3',
|
||||
tags: ['tag1'],
|
||||
},
|
||||
'lib'
|
||||
)
|
||||
.build();
|
||||
|
||||
await showProjectsHandler({
|
||||
projects: ['tag:tag1'],
|
||||
});
|
||||
|
||||
expect(console.log).toHaveBeenCalledWith('proj1');
|
||||
expect(console.log).toHaveBeenCalledWith('proj3');
|
||||
expect(console.log).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should list projects with specific target', async () => {
|
||||
graph = new GraphBuilder()
|
||||
.addProjectConfiguration(
|
||||
{
|
||||
root: 'proj1',
|
||||
name: 'proj1',
|
||||
targets: {
|
||||
build: {
|
||||
executor: 'build',
|
||||
},
|
||||
},
|
||||
},
|
||||
'app'
|
||||
)
|
||||
.addProjectConfiguration(
|
||||
{
|
||||
root: 'proj2',
|
||||
name: 'proj2',
|
||||
targets: {
|
||||
build: {
|
||||
executor: 'build',
|
||||
},
|
||||
},
|
||||
},
|
||||
'lib'
|
||||
)
|
||||
.addProjectConfiguration(
|
||||
{
|
||||
root: 'proj3',
|
||||
name: 'proj3',
|
||||
targets: {
|
||||
test: {
|
||||
executor: 'test',
|
||||
},
|
||||
},
|
||||
},
|
||||
'lib'
|
||||
)
|
||||
.build();
|
||||
|
||||
await showProjectsHandler({
|
||||
withTarget: ['build'],
|
||||
});
|
||||
|
||||
expect(console.log).toHaveBeenCalledWith('proj1');
|
||||
expect(console.log).toHaveBeenCalledWith('proj2');
|
||||
expect(console.log).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
class GraphBuilder {
|
||||
nodes: Record<string, ProjectGraphProjectNode> = {};
|
||||
|
||||
addProjectConfiguration(
|
||||
project: ProjectConfiguration,
|
||||
type: ProjectGraph['nodes'][string]['type']
|
||||
) {
|
||||
this.nodes[project.name] = {
|
||||
name: project.name,
|
||||
type,
|
||||
data: { ...project },
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
build(): ProjectGraph {
|
||||
return {
|
||||
nodes: this.nodes,
|
||||
dependencies: {},
|
||||
externalNodes: {},
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -18,8 +18,7 @@ import {
|
||||
splitArgsIntoNxArgsAndOverrides,
|
||||
} from '../../utils/command-line-utils';
|
||||
import { findMatchingProjects } from '../../utils/find-matching-projects';
|
||||
import { ShowProjectOptions, ShowProjectsOptions } from './command-object';
|
||||
import { generateGraph } from '../graph/graph';
|
||||
import { ShowProjectsOptions } from './command-object';
|
||||
|
||||
export async function showProjectsHandler(
|
||||
args: ShowProjectsOptions
|
||||
@ -75,76 +74,15 @@ export async function showProjectsHandler(
|
||||
|
||||
if (args.json) {
|
||||
console.log(JSON.stringify(Array.from(selectedProjects)));
|
||||
} else if (args.sep) {
|
||||
console.log(Array.from(selectedProjects.values()).join(args.sep));
|
||||
} else {
|
||||
for (const project of selectedProjects) {
|
||||
console.log(project);
|
||||
}
|
||||
}
|
||||
|
||||
await output.drain();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
export async function showProjectHandler(
|
||||
args: ShowProjectOptions
|
||||
): Promise<void> {
|
||||
const graph = await createProjectGraphAsync();
|
||||
const node = graph.nodes[args.projectName];
|
||||
if (!node) {
|
||||
console.log(`Could not find project ${args.projectName}`);
|
||||
process.exit(1);
|
||||
}
|
||||
if (args.json) {
|
||||
console.log(JSON.stringify(node.data));
|
||||
} else if (args.web) {
|
||||
await generateGraph(
|
||||
{
|
||||
view: 'project-details',
|
||||
focus: node.name,
|
||||
watch: true,
|
||||
open: true,
|
||||
},
|
||||
[]
|
||||
);
|
||||
} else {
|
||||
const chalk = require('chalk') as typeof import('chalk');
|
||||
const logIfExists = (label, key: keyof typeof node['data']) => {
|
||||
if (node.data[key]) {
|
||||
console.log(`${chalk.bold(label)}: ${node.data[key]}`);
|
||||
}
|
||||
};
|
||||
|
||||
logIfExists('Name', 'name');
|
||||
logIfExists('Root', 'root');
|
||||
logIfExists('Source Root', 'sourceRoot');
|
||||
logIfExists('Tags', 'tags');
|
||||
logIfExists('Implicit Dependencies', 'implicitDependencies');
|
||||
|
||||
const targets = Object.entries(node.data.targets ?? {});
|
||||
const maxTargetNameLength = Math.max(...targets.map(([t]) => t.length));
|
||||
const maxExecutorNameLength = Math.max(
|
||||
...targets.map(([, t]) => t?.executor?.length ?? 0)
|
||||
);
|
||||
|
||||
if (targets.length > 0) {
|
||||
console.log(`${chalk.bold('Targets')}: `);
|
||||
for (const [target, targetConfig] of targets) {
|
||||
console.log(
|
||||
`- ${chalk.bold((target + ':').padEnd(maxTargetNameLength + 2))} ${(
|
||||
targetConfig?.executor ?? ''
|
||||
).padEnd(maxExecutorNameLength + 2)} ${(() => {
|
||||
const configurations = Object.keys(
|
||||
targetConfig.configurations ?? {}
|
||||
);
|
||||
if (configurations.length) {
|
||||
return chalk.dim(configurations.join(', '));
|
||||
}
|
||||
return '';
|
||||
})()}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
function getGraphNodesMatchingPatterns(
|
||||
Loading…
x
Reference in New Issue
Block a user