feat(graph): show script content in header (#23257)

<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
![Screenshot 2024-05-15 at 10 32
54 AM](https://github.com/nrwl/nx/assets/16211801/1ef4d67f-94e5-46bd-bcee-b966984e86cd)

<img width="1023" alt="Screenshot 2024-05-08 at 5 53 06 PM"
src="https://github.com/nrwl/nx/assets/16211801/cb9161bd-fc4a-4965-89f7-09ce8d72226e">

<img width="585" alt="Screenshot 2024-05-08 at 5 52 51 PM"
src="https://github.com/nrwl/nx/assets/16211801/760ebe19-1c29-4fad-ba9d-174b8303a362">


## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
This commit is contained in:
Emily Xiong 2024-05-21 18:11:04 -04:00 committed by GitHub
parent 992ae85192
commit 08ef0e4bde
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 407 additions and 130 deletions

View File

@ -14,6 +14,8 @@ import { Pill } from '../pill';
import { TargetTechnologies } from '../target-technologies/target-technologies'; import { TargetTechnologies } from '../target-technologies/target-technologies';
import { SourceInfo } from '../source-info/source-info'; import { SourceInfo } from '../source-info/source-info';
import { CopyToClipboard } from '../copy-to-clipboard/copy-to-clipboard'; import { CopyToClipboard } from '../copy-to-clipboard/copy-to-clipboard';
import { getDisplayHeaderFromTargetConfiguration } from '../utils/get-display-header-from-target-configuration';
import { TargetExecutor } from '../target-executor/target-executor';
export interface TargetConfigurationDetailsHeaderProps { export interface TargetConfigurationDetailsHeaderProps {
isCollasped: boolean; isCollasped: boolean;
@ -52,10 +54,8 @@ export const TargetConfigurationDetailsHeader = ({
isCollasped = false; isCollasped = false;
} }
const singleCommand = const { command, commands, script, executor } =
targetConfiguration.executor === 'nx:run-commands' getDisplayHeaderFromTargetConfiguration(targetConfiguration);
? targetConfiguration.command ?? targetConfiguration.options?.command
: null;
return ( return (
<header <header
@ -86,7 +86,13 @@ export const TargetConfigurationDetailsHeader = ({
{isCollasped && {isCollasped &&
targetConfiguration?.executor !== '@nx/js:release-publish' && ( targetConfiguration?.executor !== '@nx/js:release-publish' && (
<p className="min-w-0 flex-1 truncate text-sm text-slate-400"> <p className="min-w-0 flex-1 truncate text-sm text-slate-400">
{singleCommand ? singleCommand : targetConfiguration.executor} <TargetExecutor
command={command}
commands={commands}
script={script}
executor={executor}
isCompact={true}
/>
</p> </p>
)} )}
{targetName === 'nx-release-publish' && ( {targetName === 'nx-release-publish' && (

View File

@ -3,21 +3,20 @@
import type { TargetConfiguration } from '@nx/devkit'; import type { TargetConfiguration } from '@nx/devkit';
import { JsonCodeBlock } from '@nx/graph/ui-code-block'; import { JsonCodeBlock } from '@nx/graph/ui-code-block';
import { useCallback, useContext, useEffect, useMemo, useState } from 'react'; import { useCallback, useContext, useEffect, useState } from 'react';
import { SourceInfo } from '../source-info/source-info'; import { SourceInfo } from '../source-info/source-info';
import { FadingCollapsible } from './fading-collapsible'; import { FadingCollapsible } from './fading-collapsible';
import { TargetConfigurationProperty } from './target-configuration-property'; import { TargetConfigurationProperty } from './target-configuration-property';
import { selectSourceInfo } from './target-configuration-details.util'; import { selectSourceInfo } from './target-configuration-details.util';
import { CopyToClipboard } from '../copy-to-clipboard/copy-to-clipboard'; import { CopyToClipboard } from '../copy-to-clipboard/copy-to-clipboard';
import { import { PropertyInfoTooltip, Tooltip } from '@nx/graph/ui-tooltips';
ExternalLink,
PropertyInfoTooltip,
Tooltip,
} from '@nx/graph/ui-tooltips';
import { TooltipTriggerText } from './tooltip-trigger-text'; import { TooltipTriggerText } from './tooltip-trigger-text';
import { Pill } from '../pill'; import { Pill } from '../pill';
import { TargetConfigurationDetailsHeader } from '../target-configuration-details-header/target-configuration-details-header'; import { TargetConfigurationDetailsHeader } from '../target-configuration-details-header/target-configuration-details-header';
import { ExpandedTargetsContext } from '@nx/graph/shared'; import { ExpandedTargetsContext } from '@nx/graph/shared';
import { getDisplayHeaderFromTargetConfiguration } from '../utils/get-display-header-from-target-configuration';
import { TargetExecutor } from '../target-executor/target-executor';
import { TargetExecutorTitle } from '../target-executor/target-executor-title';
interface TargetConfigurationDetailsProps { interface TargetConfigurationDetailsProps {
projectName: string; projectName: string;
@ -71,36 +70,8 @@ export default function TargetConfigurationDetails({
} }
}, [expandedTargets, targetName, collapsable]); }, [expandedTargets, targetName, collapsable]);
let executorLink: string | null = null; const { link, options, script, ...displayHeader } =
getDisplayHeaderFromTargetConfiguration(targetConfiguration);
// TODO: Handle this better because this will not work with labs
if (targetConfiguration.executor?.startsWith('@nx/')) {
const packageName = targetConfiguration.executor
.split('/')[1]
.split(':')[0];
const executorName = targetConfiguration.executor
.split('/')[1]
.split(':')[1];
executorLink = `https://nx.dev/nx-api/${packageName}/executors/${executorName}`;
} else if (targetConfiguration.executor === 'nx:run-commands') {
executorLink = `https://nx.dev/nx-api/nx/executors/run-commands`;
} else if (targetConfiguration.executor === 'nx:run-script') {
executorLink = `https://nx.dev/nx-api/nx/executors/run-script`;
}
const singleCommand =
targetConfiguration.executor === 'nx:run-commands'
? targetConfiguration.command ?? targetConfiguration.options?.command
: null;
const options = useMemo(() => {
if (singleCommand) {
const { command, ...rest } = targetConfiguration.options;
return rest;
} else {
return targetConfiguration.options;
}
}, [targetConfiguration.options, singleCommand]);
const configurations = targetConfiguration.configurations; const configurations = targetConfiguration.configurations;
const shouldRenderOptions = const shouldRenderOptions =
@ -132,48 +103,30 @@ export default function TargetConfigurationDetails({
<div className="p-4 text-base"> <div className="p-4 text-base">
<div className="group mb-4"> <div className="group mb-4">
<h4 className="mb-4"> <h4 className="mb-4">
{singleCommand ? ( <TargetExecutorTitle
<span className="font-medium"> {...displayHeader}
Command handleCopyClick={handleCopyClick}
<span className="mb-1 ml-2 hidden group-hover:inline"> />
<CopyToClipboard
onCopy={() =>
handleCopyClick(`"command": "${singleCommand}"`)
}
/>
</span>
</span>
) : (
<Tooltip
openAction="hover"
content={(<PropertyInfoTooltip type="executors" />) as any}
>
<span className="font-medium">
<TooltipTriggerText>Executor</TooltipTriggerText>
</span>
</Tooltip>
)}
</h4> </h4>
<p className="pl-5 font-mono"> <p className="pl-5 font-mono">
{executorLink ? ( <TargetExecutor {...displayHeader} link={link} />
<span>
<ExternalLink
href={executorLink ?? 'https://nx.dev/nx-api'}
text={
singleCommand
? singleCommand
: targetConfiguration.executor
}
/>
</span>
) : singleCommand ? (
singleCommand
) : (
targetConfiguration.executor
)}
</p> </p>
</div> </div>
{script && (
<div className="group mb-4">
<h4 className="mb-4">
<TargetExecutorTitle
script={script}
handleCopyClick={handleCopyClick}
/>
</h4>
<p className="pl-5 font-mono">
<TargetExecutor script={script} link={link} />
</p>
</div>
)}
{targetConfiguration.inputs && ( {targetConfiguration.inputs && (
<div className="group"> <div className="group">
<h4 className="mb-4"> <h4 className="mb-4">

View File

@ -0,0 +1,32 @@
import type { Meta, StoryObj } from '@storybook/react';
import { TargetExecutorTitle } from './target-executor-title';
const meta: Meta<typeof TargetExecutorTitle> = {
component: TargetExecutorTitle,
title: 'TargetExecutorTitle',
};
export default meta;
type Story = StoryObj<typeof TargetExecutorTitle>;
export const Command: Story = {
args: {
command: 'nx run my-app:build',
},
};
export const Commands: Story = {
args: {
commands: ['nx run my-app:build', 'nx run my-app:test'],
},
};
export const Script: Story = {
args: {
script: 'nx run my-app:build',
},
};
export const Executor: Story = {
args: {},
};

View File

@ -0,0 +1,64 @@
import { PropertyInfoTooltip, Tooltip } from '@nx/graph/ui-tooltips';
import { CopyToClipboard } from '../copy-to-clipboard/copy-to-clipboard';
import { TooltipTriggerText } from '../target-configuration-details/tooltip-trigger-text';
export function TargetExecutorTitle({
commands,
command,
script,
handleCopyClick,
}: {
handleCopyClick: (copyText: string) => void;
commands?: string[];
command?: string;
script?: string;
}) {
if (commands && commands.length) {
return (
<span className="font-medium">
Commands
<span className="mb-1 ml-2 hidden group-hover:inline">
<CopyToClipboard
onCopy={() =>
handleCopyClick(
`"commands": [${commands.map((c) => `"${c}"`).join(', ')}]`
)
}
/>
</span>
</span>
);
}
if (command) {
return (
<span className="font-medium">
Command
<span className="mb-1 ml-2 hidden group-hover:inline">
<CopyToClipboard
onCopy={() => handleCopyClick(`"command": "${command}"`)}
/>
</span>
</span>
);
}
if (script) {
return (
<span className="font-medium">
Script
<span className="mb-1 ml-2 hidden group-hover:inline">
<CopyToClipboard onCopy={() => handleCopyClick(script)} />
</span>
</span>
);
}
return (
<Tooltip
openAction="hover"
content={(<PropertyInfoTooltip type="executors" />) as any}
>
<span className="font-medium">
<TooltipTriggerText>Executor</TooltipTriggerText>
</span>
</Tooltip>
);
}

View File

@ -0,0 +1,34 @@
import type { Meta, StoryObj } from '@storybook/react';
import { TargetExecutor } from './target-executor';
const meta: Meta<typeof TargetExecutor> = {
component: TargetExecutor,
title: 'TargetExecutor',
};
export default meta;
type Story = StoryObj<typeof TargetExecutor>;
export const Command: Story = {
args: {
command: 'nx run my-app:build',
},
};
export const Commands: Story = {
args: {
commands: ['nx run my-app:build', 'nx run my-app:test'],
},
};
export const Script: Story = {
args: {
script: 'nx run my-app:build',
},
};
export const Executor: Story = {
args: {
executor: 'nx run my-app:build',
},
};

View File

@ -0,0 +1,49 @@
import { ExternalLink } from '@nx/graph/ui-tooltips';
export interface TargetExecutorProps {
command?: string;
commands?: string[];
script?: string;
executor?: string;
isCompact?: boolean;
link?: string;
}
export function TargetExecutor({
command,
commands,
script,
executor,
isCompact,
link,
}: TargetExecutorProps) {
if (commands) {
if (isCompact) {
return link ? (
<ExternalLink href={link}>
{commands.length === 1 ? commands[0] : executor}
</ExternalLink>
) : commands.length === 1 ? (
commands[0]
) : (
executor
);
}
return (
<ul>
{commands?.map((c) =>
c ? (
<li>{link ? <ExternalLink href={link}>{c}</ExternalLink> : c}</li>
) : null
)}
</ul>
);
}
const displayText = command ?? script ?? executor ?? '';
return link ? (
<ExternalLink href={link}>{displayText}</ExternalLink>
) : (
displayText
);
}

View File

@ -0,0 +1,56 @@
/* eslint-disable @nx/enforce-module-boundaries */
// nx-ignore-next-line
import type { TargetConfiguration } from '@nx/devkit';
export function getDisplayHeaderFromTargetConfiguration(
targetConfiguration: TargetConfiguration
): {
link: string | undefined;
options: Record<string, any>;
command?: string;
commands?: string[];
script?: string;
executor?: string;
} {
let link: string | undefined;
let displayText: {
command?: string;
commands?: string[];
script?: string;
executor?: string;
} = {
executor: targetConfiguration.executor,
};
let options = targetConfiguration.options;
// TODO: Handle this better because this will not work with labs
if (targetConfiguration.executor?.startsWith('@nx/')) {
const packageName = targetConfiguration.executor
.split('/')[1]
.split(':')[0];
const executorName = targetConfiguration.executor
.split('/')[1]
.split(':')[1];
link = `https://nx.dev/nx-api/${packageName}/executors/${executorName}`;
} else if (targetConfiguration.executor === 'nx:run-commands') {
link = `https://nx.dev/nx-api/nx/executors/run-commands`;
displayText.command =
targetConfiguration.command ?? targetConfiguration.options?.command;
displayText.commands = targetConfiguration.options?.commands?.map(
(c: { command: string }) => c.command ?? c
);
const { command, commands, ...rest } = targetConfiguration.options;
options = rest;
} else if (targetConfiguration.executor === 'nx:run-script') {
link = `https://nx.dev/nx-api/nx/executors/run-script`;
displayText.command = targetConfiguration.metadata?.runCommand;
displayText.script = targetConfiguration.metadata?.scriptContent;
const { script, ...rest } = targetConfiguration.options;
options = rest;
}
return {
...displayText,
link,
options,
};
}

View File

@ -1,11 +1,11 @@
import { ArrowTopRightOnSquareIcon } from '@heroicons/react/24/outline'; import { ArrowTopRightOnSquareIcon } from '@heroicons/react/24/outline';
export function ExternalLink({ export function ExternalLink({
text, children,
href, href,
title, title,
}: { }: {
text: string; children?: React.ReactNode;
href: string; href: string;
title?: string; title?: string;
}) { }) {
@ -13,10 +13,11 @@ export function ExternalLink({
<a <a
href={href} href={href}
title={title} title={title}
className="text-slate-500 dark:text-slate-400 hover:underline inline-flex items-center gap-2" className="gap-2 text-slate-500 hover:underline dark:text-slate-400"
target="_blank" target="_blank"
rel="noreferrer"
> >
{text} <ArrowTopRightOnSquareIcon className="w-4 h-4 inline" /> {children} <ArrowTopRightOnSquareIcon className="inline h-4 w-4" />
</a> </a>
); );
} }

View File

@ -106,13 +106,10 @@ export function PropertyInfoTooltip({ type }: PropertyInfoTooltipProps) {
{propertyInfo.docsUrl ? ( {propertyInfo.docsUrl ? (
<div className="flex py-2"> <div className="flex py-2">
<p className="flex items-center pr-4"> <p className="flex items-center pr-4">
<ExternalLink <ExternalLink href={propertyInfo.docsUrl}>
text={ {propertyInfo.docsLinkText ??
propertyInfo.docsLinkText ?? `Learn more about ${propertyInfo.heading}`}
`Learn more about ${propertyInfo.heading}` </ExternalLink>
}
href={propertyInfo.docsUrl}
/>
</p> </p>
</div> </div>
) : null} ) : null}

View File

@ -30,10 +30,9 @@ export function SourcemapInfoToolTip({
<span className="font-bold">{isTarget ? 'Created' : 'Set'} by:</span> <span className="font-bold">{isTarget ? 'Created' : 'Set'} by:</span>
<span className="inline-flex grow items-center justify-between"> <span className="inline-flex grow items-center justify-between">
{docsUrlSlug ? ( {docsUrlSlug ? (
<ExternalLink <ExternalLink href={`https://nx.dev/nx-api/${docsUrlSlug}`}>
text={plugin} {plugin}
href={`https://nx.dev/nx-api/${docsUrlSlug}`} </ExternalLink>
/>
) : ( ) : (
`${plugin}` `${plugin}`
)} )}
@ -46,7 +45,7 @@ export function SourcemapInfoToolTip({
); );
return ( return (
<div className="max-w-md text-sm text-slate-700 dark:text-slate-400 sm:max-w-full"> <div className="max-w-md text-sm text-slate-700 sm:max-w-full dark:text-slate-400">
<div <div
className={twMerge( className={twMerge(
`flex flex-col py-2 font-mono`, `flex flex-col py-2 font-mono`,
@ -58,10 +57,9 @@ export function SourcemapInfoToolTip({
{showLink && ( {showLink && (
<div className="flex py-2"> <div className="flex py-2">
<p className={`flex flex-col gap-1`}> <p className={`flex flex-col gap-1`}>
<ExternalLink <ExternalLink href="https://nx.dev/concepts/inferred-tasks">
text="Learn more about how projects are configured" Learn more about how projects are configured
href="https://nx.dev/concepts/inferred-tasks" </ExternalLink>
/>
</p> </p>
</div> </div>
)} )}

View File

@ -124,6 +124,7 @@ export interface ProjectMetadata {
} }
export interface TargetMetadata { export interface TargetMetadata {
[k: string]: any;
description?: string; description?: string;
technologies?: string[]; technologies?: string[];
} }

View File

@ -48,6 +48,10 @@ describe('nx package.json workspaces plugin', () => {
"targets": { "targets": {
"echo": { "echo": {
"executor": "nx:run-script", "executor": "nx:run-script",
"metadata": {
"runCommand": "npm run echo",
"scriptContent": "echo root project",
},
"options": { "options": {
"script": "echo", "script": "echo",
}, },
@ -83,6 +87,10 @@ describe('nx package.json workspaces plugin', () => {
}, },
"test": { "test": {
"executor": "nx:run-script", "executor": "nx:run-script",
"metadata": {
"runCommand": "npm run test",
"scriptContent": "jest",
},
"options": { "options": {
"script": "test", "script": "test",
}, },
@ -111,6 +119,10 @@ describe('nx package.json workspaces plugin', () => {
"targets": { "targets": {
"build": { "build": {
"executor": "nx:run-script", "executor": "nx:run-script",
"metadata": {
"runCommand": "npm run build",
"scriptContent": "tsc",
},
"options": { "options": {
"script": "build", "script": "build",
}, },
@ -127,6 +139,10 @@ describe('nx package.json workspaces plugin', () => {
}, },
"test": { "test": {
"executor": "nx:run-script", "executor": "nx:run-script",
"metadata": {
"runCommand": "npm run test",
"scriptContent": "jest",
},
"options": { "options": {
"script": "test", "script": "test",
}, },

View File

@ -58,6 +58,10 @@ describe('nx project.json plugin', () => {
}, },
"test": { "test": {
"executor": "nx:run-script", "executor": "nx:run-script",
"metadata": {
"runCommand": "npm run test",
"scriptContent": "jest",
},
"options": { "options": {
"script": "test", "script": "test",
}, },

View File

@ -78,6 +78,7 @@ describe('target-defaults plugin', () => {
"^build", "^build",
], ],
"executor": "nx:run-commands", "executor": "nx:run-commands",
"metadata": {},
}, },
}, },
}, },
@ -121,6 +122,10 @@ describe('target-defaults plugin', () => {
"test": { "test": {
"command": "jest", "command": "jest",
"executor": "nx:run-script", "executor": "nx:run-script",
"metadata": {
"runCommand": "npm run test",
"scriptContent": "nx affected:test",
},
"options": { "options": {
"script": "test", "script": "test",
}, },
@ -169,6 +174,10 @@ describe('target-defaults plugin', () => {
"test": { "test": {
"command": "jest", "command": "jest",
"executor": "nx:run-script", "executor": "nx:run-script",
"metadata": {
"runCommand": "npm run test",
"scriptContent": "nx affected:test",
},
"options": { "options": {
"script": "test", "script": "test",
}, },
@ -380,12 +389,14 @@ describe('target-defaults plugin', () => {
"targets": { "targets": {
"echo": { "echo": {
"executor": "nx:run-commands", "executor": "nx:run-commands",
"metadata": {},
"options": { "options": {
"cwd": "{projectRoot}", "cwd": "{projectRoot}",
}, },
}, },
"echo2": { "echo2": {
"executor": "nx:run-commands", "executor": "nx:run-commands",
"metadata": {},
"options": { "options": {
"cwd": "{projectRoot}", "cwd": "{projectRoot}",
}, },
@ -445,12 +456,14 @@ describe('target-defaults plugin', () => {
"targets": { "targets": {
"echo": { "echo": {
"executor": "nx:run-commands", "executor": "nx:run-commands",
"metadata": {},
"options": { "options": {
"cwd": "{projectRoot}", "cwd": "{projectRoot}",
}, },
}, },
"echo2": { "echo2": {
"executor": "nx:run-commands", "executor": "nx:run-commands",
"metadata": {},
"options": { "options": {
"cwd": "{projectRoot}", "cwd": "{projectRoot}",
}, },
@ -483,6 +496,7 @@ describe('target-defaults plugin', () => {
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
{ {
"command": "echo hi", "command": "echo hi",
"metadata": {},
} }
`); `);
}); });
@ -504,6 +518,7 @@ describe('target-defaults plugin', () => {
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
{ {
"executor": "nx:run-commands", "executor": "nx:run-commands",
"metadata": {},
"options": { "options": {
"command": "echo hi", "command": "echo hi",
}, },
@ -524,6 +539,7 @@ describe('target-defaults plugin', () => {
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
{ {
"executor": "nx:run-script", "executor": "nx:run-script",
"metadata": {},
"options": { "options": {
"script": "build", "script": "build",
}, },

View File

@ -167,10 +167,15 @@ export function getTargetInfo(
...packageJsonTarget?.options, ...packageJsonTarget?.options,
...projectJsonTarget?.options, ...projectJsonTarget?.options,
}; };
const metadata = {
...packageJsonTarget?.metadata,
...projectJsonTarget?.metadata,
};
if (projectJsonTarget?.command) { if (projectJsonTarget?.command) {
return { return {
command: projectJsonTarget?.command, command: projectJsonTarget?.command,
metadata,
}; };
} }
@ -181,6 +186,7 @@ export function getTargetInfo(
options: { options: {
command: targetOptions?.command, command: targetOptions?.command,
}, },
metadata,
}; };
} else if (targetOptions?.commands) { } else if (targetOptions?.commands) {
return { return {
@ -188,10 +194,12 @@ export function getTargetInfo(
options: { options: {
commands: targetOptions.commands, commands: targetOptions.commands,
}, },
metadata,
}; };
} }
return { return {
executor: 'nx:run-commands', executor: 'nx:run-commands',
metadata,
}; };
} }
@ -201,6 +209,7 @@ export function getTargetInfo(
options: { options: {
script: targetOptions?.script ?? target, script: targetOptions?.script ?? target,
}, },
metadata,
}; };
} }

View File

@ -29,23 +29,35 @@ describe('readTargetsFromPackageJson', () => {
options: { options: {
script: 'build', script: 'build',
}, },
metadata: {
runCommand: 'npm run build',
scriptContent: 'echo 1',
},
}; };
it('should read targets from project.json and package.json', () => { it('should read targets from project.json and package.json', () => {
const result = readTargetsFromPackageJson(packageJson); const result = readTargetsFromPackageJson(packageJson);
expect(result).toEqual({ expect(result).toMatchInlineSnapshot(`
build: { {
executor: 'nx:run-script', "build": {
options: { "executor": "nx:run-script",
script: 'build', "metadata": {
"runCommand": "npm run build",
"scriptContent": "echo 1",
},
"options": {
"script": "build",
},
}, },
}, "nx-release-publish": {
'nx-release-publish': { "dependsOn": [
dependsOn: ['^nx-release-publish'], "^nx-release-publish",
executor: '@nx/js:release-publish', ],
options: {}, "executor": "@nx/js:release-publish",
}, "options": {},
}); },
}
`);
}); });
it('should contain extended options from nx property in package.json', () => { it('should contain extended options from nx property in package.json', () => {
@ -86,17 +98,27 @@ describe('readTargetsFromPackageJson', () => {
}, },
}); });
expect(result).toEqual({ expect(result).toMatchInlineSnapshot(`
test: { {
executor: 'nx:run-script', "nx-release-publish": {
options: { script: 'test' }, "dependsOn": [
}, "^nx-release-publish",
'nx-release-publish': { ],
dependsOn: ['^nx-release-publish'], "executor": "@nx/js:release-publish",
executor: '@nx/js:release-publish', "options": {},
options: {}, },
}, "test": {
}); "executor": "nx:run-script",
"metadata": {
"runCommand": "npm run test",
"scriptContent": "echo testing",
},
"options": {
"script": "test",
},
},
}
`);
}); });
it('should extend script based targets if matching config', () => { it('should extend script based targets if matching config', () => {
@ -117,6 +139,10 @@ describe('readTargetsFromPackageJson', () => {
expect(result.build).toMatchInlineSnapshot(` expect(result.build).toMatchInlineSnapshot(`
{ {
"executor": "nx:run-script", "executor": "nx:run-script",
"metadata": {
"runCommand": "npm run build",
"scriptContent": "echo 1",
},
"options": { "options": {
"script": "build", "script": "build",
}, },

View File

@ -7,6 +7,7 @@ import {
import { mergeTargetConfigurations } from '../project-graph/utils/project-configuration-utils'; import { mergeTargetConfigurations } from '../project-graph/utils/project-configuration-utils';
import { readJsonFile } from './fileutils'; import { readJsonFile } from './fileutils';
import { getNxRequirePaths } from './installation-directory'; import { getNxRequirePaths } from './installation-directory';
import { getPackageManagerCommand } from './package-manager';
export interface NxProjectPackageJsonConfiguration { export interface NxProjectPackageJsonConfiguration {
implicitDependencies?: string[]; implicitDependencies?: string[];
@ -115,12 +116,20 @@ export function readNxMigrateConfig(
}; };
} }
export function buildTargetFromScript(script: string): TargetConfiguration { export function buildTargetFromScript(
script: string,
scripts: Record<string, string> = {}
): TargetConfiguration {
const packageManagerCommand = getPackageManagerCommand();
return { return {
executor: 'nx:run-script', executor: 'nx:run-script',
options: { options: {
script, script,
}, },
metadata: {
scriptContent: scripts[script],
runCommand: packageManagerCommand.run(script),
},
}; };
} }
@ -130,7 +139,7 @@ export function readTargetsFromPackageJson(packageJson: PackageJson) {
const includedScripts = nx?.includedScripts || Object.keys(scripts ?? {}); const includedScripts = nx?.includedScripts || Object.keys(scripts ?? {});
// //
for (const script of includedScripts) { for (const script of includedScripts) {
res[script] = buildTargetFromScript(script); res[script] = buildTargetFromScript(script, scripts);
} }
for (const targetName in nx?.targets) { for (const targetName in nx?.targets) {
res[targetName] = mergeTargetConfigurations( res[targetName] = mergeTargetConfigurations(

View File

@ -26,7 +26,7 @@ export interface PackageManagerCommands {
exec: string; exec: string;
dlx: string; dlx: string;
list: string; list: string;
run: (script: string, args: string) => string; run: (script: string, args?: string) => string;
getRegistryUrl: string; getRegistryUrl: string;
} }
@ -100,7 +100,8 @@ export function getPackageManagerCommand(
rm: 'yarn remove', rm: 'yarn remove',
exec: 'yarn', exec: 'yarn',
dlx: useBerry ? 'yarn dlx' : 'yarn', dlx: useBerry ? 'yarn dlx' : 'yarn',
run: (script: string, args: string) => `yarn ${script} ${args}`, run: (script: string, args?: string) =>
`yarn ${script}${args ? ` ${args}` : ''}`,
list: useBerry ? 'yarn info --name-only' : 'yarn list', list: useBerry ? 'yarn info --name-only' : 'yarn list',
getRegistryUrl: useBerry getRegistryUrl: useBerry
? 'yarn config get npmRegistryServer' ? 'yarn config get npmRegistryServer'
@ -122,10 +123,14 @@ export function getPackageManagerCommand(
rm: 'pnpm rm', rm: 'pnpm rm',
exec: modernPnpm ? 'pnpm exec' : 'pnpx', exec: modernPnpm ? 'pnpm exec' : 'pnpx',
dlx: modernPnpm ? 'pnpm dlx' : 'pnpx', dlx: modernPnpm ? 'pnpm dlx' : 'pnpx',
run: (script: string, args: string) => run: (script: string, args?: string) =>
includeDoubleDashBeforeArgs `pnpm run ${script}${
? `pnpm run ${script} -- ${args}` args
: `pnpm run ${script} ${args}`, ? includeDoubleDashBeforeArgs
? ' -- ' + args
: ` ${args}`
: ''
}`,
list: 'pnpm ls --depth 100', list: 'pnpm ls --depth 100',
getRegistryUrl: 'pnpm config get registry', getRegistryUrl: 'pnpm config get registry',
}; };
@ -143,7 +148,8 @@ export function getPackageManagerCommand(
rm: 'npm rm', rm: 'npm rm',
exec: 'npx', exec: 'npx',
dlx: 'npx', dlx: 'npx',
run: (script: string, args: string) => `npm run ${script} -- ${args}`, run: (script: string, args?: string) =>
`npm run ${script}${args ? ' -- ' + args : ''}`,
list: 'npm ls', list: 'npm ls',
getRegistryUrl: 'npm config get registry', getRegistryUrl: 'npm config get registry',
}; };