feat(graph): rework pdv target section & remove unused code (#21159)

This commit is contained in:
MaxKless 2024-01-16 21:12:34 +01:00 committed by GitHub
parent 253c0ff2ab
commit e38b0bb6f4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
41 changed files with 1914 additions and 1209 deletions

View File

@ -81,6 +81,14 @@ export class ExternalApiImpl extends ExternalApi {
} }
} }
openProjectDetails(projectName: string, targetName?: string) {
this.router.navigate(
`/project-details/${encodeURIComponent(projectName)}${
targetName ? `?expanded=${encodeURIComponent(targetName)}` : ''
}`
);
}
focusProject(projectName: string) { focusProject(projectName: string) {
this.router.navigate(`/projects/${encodeURIComponent(projectName)}`); this.router.navigate(`/projects/${encodeURIComponent(projectName)}`);
} }

View File

@ -1,288 +0,0 @@
import {
ChevronDownIcon,
ChevronRightIcon,
EyeIcon,
PlayIcon,
} from '@heroicons/react/24/outline';
import { getSourceInformation } from './get-source-information';
import useMapState from './use-map-state';
import {
getExternalApiService,
useEnvironmentConfig,
useRouteConstructor,
} from '@nx/graph/shared';
import { useNavigate } from 'react-router-dom';
import { get } from 'http';
import { useEffect } from 'react';
interface JsonLineRendererProps {
jsonData: any;
sourceMap: Record<string, string[]>;
}
export function JsonLineRenderer(props: JsonLineRendererProps) {
let collapsibleSections = new Map<number, number>();
let lines: [string, number][] = [];
let currentLine = 0;
let lineToPropertyPathMap = new Map<number, string>();
let lineToInteractionMap = new Map<
number,
{ target: string; configuration?: string }
>();
const [getCollapsed, setCollapsed] = useMapState<number, boolean>();
const { environment } = useEnvironmentConfig();
const externalApiService = getExternalApiService();
const navigate = useNavigate();
const routeContructor = useRouteConstructor();
function add(value: string, depth: number) {
if (lines.length === currentLine) {
lines.push(['', depth]);
}
lines[currentLine] = [lines[currentLine][0] + value, depth];
}
function processJson(
jsonData: any,
depth = 0,
propertyPath = '',
isLast = false
) {
if (Array.isArray(jsonData)) {
const sectionStart = currentLine;
add('[', depth);
currentLine++;
jsonData.forEach((value, index) => {
const newPropertyPath = `${
propertyPath ? propertyPath + '.' : ''
}${value}`;
lineToPropertyPathMap.set(currentLine, newPropertyPath);
processJson(
value,
depth + 1,
newPropertyPath,
index === jsonData.length - 1
);
});
add(']', depth);
if (!isLast) {
add(',', depth);
}
const sectionEnd = currentLine;
collapsibleSections.set(sectionStart, sectionEnd);
currentLine++;
} else if (jsonData && typeof jsonData === 'object') {
const sectionStart = currentLine;
add('{', depth);
currentLine++;
Object.entries(jsonData).forEach(([key, value], index, array) => {
// skip empty objects
if (
Object.keys(value as any).length === 0 &&
typeof value === 'object'
) {
return;
}
// skip certain root properties
if (
depth === 0 &&
(key === 'sourceRoot' ||
key === 'name' ||
key === '$schema' ||
key === 'tags')
) {
return;
}
add(`"${key}": `, depth);
if (propertyPath === 'targets') {
lineToInteractionMap.set(currentLine, { target: key });
}
if (propertyPath.match(/^targets\..*configurations$/)) {
lineToInteractionMap.set(currentLine, {
target: propertyPath.split('.')[1],
configuration: key,
});
}
const newPropertyPath = `${
propertyPath ? propertyPath + '.' : ''
}${key}`;
lineToPropertyPathMap.set(currentLine, newPropertyPath);
processJson(
value,
depth + 1,
newPropertyPath,
index === array.length - 1
);
});
add('}', depth);
if (!isLast) {
add(',', depth);
}
const sectionEnd = currentLine;
collapsibleSections.set(sectionStart, sectionEnd);
currentLine++;
} else {
add(`"${jsonData}"`, depth);
if (!isLast) {
add(',', depth);
}
currentLine++;
}
}
processJson(props.jsonData);
console.log(lineToInteractionMap);
// start off with all targets & configurations collapsed~
useEffect(() => {
for (const line of lineToInteractionMap.keys()) {
if (!getCollapsed(line)) {
setCollapsed(line, true);
}
}
}, []);
function toggleCollapsed(index: number) {
setCollapsed(index, !getCollapsed(index));
}
function lineIsCollapsed(index: number) {
for (const [start, end] of collapsibleSections) {
if (index > start && index < end) {
if (getCollapsed(start)) {
return true;
}
}
}
return false;
}
function runTarget({
target,
configuration,
}: {
target: string;
configuration?: string;
}) {
const projectName = props.jsonData.name;
externalApiService.postEvent({
type: 'run-task',
payload: { taskId: `${projectName}:${target}` },
});
}
function viewInTaskGraph({
target,
configuration,
}: {
target: string;
configuration?: string;
}) {
const projectName = props.jsonData.name;
if (environment === 'nx-console') {
externalApiService.postEvent({
type: 'open-task-graph',
payload: {
projectName: projectName,
targetName: target,
},
});
} else {
navigate(
routeContructor(
{
pathname: `/tasks/${encodeURIComponent(target)}`,
search: `?projects=${encodeURIComponent(projectName)}`,
},
true
)
);
}
}
return (
<div className="overflow-auto w-full h-full flex">
<div className="h-fit min-h-full w-12 shrink-0 pr-2 border-solid border-r-2 border-slate-700">
{lines.map(([text, indentation], index) => {
if (
lineIsCollapsed(index) ||
index === 0 ||
index === lines.length - 1
) {
return null;
}
const canCollapse =
collapsibleSections.has(index) &&
collapsibleSections.get(index)! - index > 1;
const interaction = lineToInteractionMap.get(index);
return (
<div className="flex justify-end items-center h-6">
{interaction?.target && !interaction?.configuration && (
<EyeIcon
className="h-4 w-4"
onClick={() => viewInTaskGraph(interaction!)}
/>
)}
{environment === 'nx-console' && interaction?.target && (
<PlayIcon
className="h-4 w-4"
onClick={() => runTarget(interaction!)}
/>
)}
{canCollapse && (
<div onClick={() => toggleCollapsed(index)} className="h-4 w-4">
{getCollapsed(index) ? (
<ChevronRightIcon />
) : (
<ChevronDownIcon />
)}
</div>
)}
</div>
);
})}
</div>
<div className="pl-2">
{lines.map(([text, indentation], index) => {
if (
lineIsCollapsed(index) ||
index === 0 ||
index === lines.length - 1
) {
return null;
}
const propertyPathAtLine = lineToPropertyPathMap.get(index);
const sourceInformation = propertyPathAtLine
? getSourceInformation(props.sourceMap, propertyPathAtLine)
: '';
return (
<pre
style={{ paddingLeft: `${indentation}rem` }}
className="group truncate hover:bg-slate-800 h-6"
>
{text}
{getCollapsed(index) ? '...' : ''}
<span className="ml-16 hidden group-hover:inline-block text-sm text-slate-500">
{sourceInformation}
</span>
</pre>
);
})}
</div>
</div>
);
}

View File

@ -1,18 +1,17 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
import { useNavigate, useRouteLoaderData } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
/* eslint-disable @nx/enforce-module-boundaries */ /* eslint-disable @nx/enforce-module-boundaries */
// nx-ignore-next-line // nx-ignore-next-line
import { ProjectGraphProjectNode } from '@nx/devkit'; import { ProjectGraphProjectNode } from '@nx/devkit';
import { EyeIcon } from '@heroicons/react/24/outline';
import { import {
getExternalApiService, getExternalApiService,
useEnvironmentConfig, useEnvironmentConfig,
useRouteConstructor, useRouteConstructor,
} from '@nx/graph/shared'; } from '@nx/graph/shared';
import { JsonLineRenderer } from './json-line-renderer';
import { EyeIcon } from '@heroicons/react/24/outline';
import PropertyRenderer from './property-renderer'; import PropertyRenderer from './property-renderer';
import Target from './target'; import Target from './target';
@ -28,7 +27,7 @@ export function ProjectDetails({
}, },
sourceMap, sourceMap,
}: ProjectDetailsProps) { }: ProjectDetailsProps) {
const { environment } = useEnvironmentConfig(); const environment = useEnvironmentConfig()?.environment;
const externalApiService = getExternalApiService(); const externalApiService = getExternalApiService();
const navigate = useNavigate(); const navigate = useNavigate();
const routeContructor = useRouteConstructor(); const routeContructor = useRouteConstructor();
@ -46,35 +45,6 @@ export function ProjectDetails({
} }
}; };
// const projectDataSorted = sortObjectWithTargetsFirst(projectData);
// return (
// <div className="flex flex-col w-full h-full">
// <div className="flex">
// <div className="w-12 pr-2 border-r-2 border-solid border-slate-700">
// <EyeIcon
// className="h-6 w-6 ml-3 mt-3"
// onClick={viewInProjectGraph}
// ></EyeIcon>
// </div>
// <div className="pl-6 pb-6">
// <h1 className="text-4xl flex items-center">
// <span>{name}</span>
// </h1>
// <div className="flex gap-2">
// <span className="text-slate-500 text-xl"> {root}</span>
// {projectData.tags?.map((tag) => (
// <div className="dark:bg-sky-500 text-white rounded px-1">
// {tag}
// </div>
// ))}
// </div>
// </div>
// </div>
// {JsonLineRenderer({ jsonData: projectDataSorted, sourceMap })}
// </div>
// );
return ( return (
<div className="m-4 overflow-auto w-full"> <div className="m-4 overflow-auto w-full">
<h1 className="text-2xl flex items-center gap-2"> <h1 className="text-2xl flex items-center gap-2">
@ -89,15 +59,17 @@ export function ProjectDetails({
</h2> </h2>
<div> <div>
<div className="mb-2"> <div className="mb-2">
<h2 className="text-xl">Targets</h2> <h2 className="text-xl mb-2">Targets</h2>
{Object.entries(projectData.targets ?? {}).map( {Object.entries(projectData.targets ?? {}).map(
([targetName, target]) => ([targetName, target]) => {
Target({ const props = {
projectName: name, projectName: name,
targetName: targetName, targetName: targetName,
targetConfiguration: target, targetConfiguration: target,
sourceMap, sourceMap,
}) };
return <Target {...props} />;
}
)} )}
</div> </div>
{Object.entries(projectData).map(([key, value]) => { {Object.entries(projectData).map(([key, value]) => {
@ -123,22 +95,4 @@ export function ProjectDetails({
); );
} }
// function sortObjectWithTargetsFirst(obj: any) {
// let sortedObj: any = {};
// // If 'targets' exists, set it as the first property
// if (obj.hasOwnProperty('targets')) {
// sortedObj.targets = obj.targets;
// }
// // Copy the rest of the properties
// for (let key in obj) {
// if (key !== 'targets') {
// sortedObj[key] = obj[key];
// }
// }
// return sortedObj;
// }
export default ProjectDetails; export default ProjectDetails;

View File

@ -54,7 +54,7 @@ type PropertValueRendererProps = PropertyRendererProps & {
function PropertyValueRenderer(props: PropertValueRendererProps) { function PropertyValueRenderer(props: PropertValueRendererProps) {
const { propertyKey, propertyValue, sourceMap, keyPrefix, nested } = props; const { propertyKey, propertyValue, sourceMap, keyPrefix, nested } = props;
if (Array.isArray(propertyValue) && propertyValue.length) { if (propertyValue && Array.isArray(propertyValue) && propertyValue.length) {
return ( return (
<div className="ml-3"> <div className="ml-3">
{nested && renderOpening(propertyValue)} {nested && renderOpening(propertyValue)}
@ -107,7 +107,7 @@ function PropertyValueRenderer(props: PropertValueRendererProps) {
} }
function renderOpening(value: any): string { function renderOpening(value: any): string {
return Array.isArray(value) && value.length return value && Array.isArray(value) && value.length
? '[' ? '['
: value && typeof value === 'object' : value && typeof value === 'object'
? '{' ? '{'
@ -115,7 +115,7 @@ function renderOpening(value: any): string {
} }
function renderClosing(value: any): string { function renderClosing(value: any): string {
return Array.isArray(value) && value.length return value && Array.isArray(value) && value.length
? '],' ? '],'
: value && typeof value === 'object' : value && typeof value === 'object'
? '},' ? '},'

View File

@ -1,8 +1,9 @@
/* eslint-disable @nx/enforce-module-boundaries */ /* eslint-disable @nx/enforce-module-boundaries */
// nx-ignore-next-line // nx-ignore-next-line
import { import {
ChevronDownIcon,
ChevronUpIcon,
EyeIcon, EyeIcon,
PencilSquareIcon,
PlayIcon, PlayIcon,
} from '@heroicons/react/24/outline'; } from '@heroicons/react/24/outline';
@ -13,8 +14,10 @@ import {
useEnvironmentConfig, useEnvironmentConfig,
useRouteConstructor, useRouteConstructor,
} from '@nx/graph/shared'; } from '@nx/graph/shared';
import { useNavigate } from 'react-router-dom'; import { Fence } from '@nx/shared-ui-fence';
import PropertyRenderer from './property-renderer'; import { useEffect, useState } from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { FadingCollapsible } from './ui/fading-collapsible.component';
/* eslint-disable-next-line */ /* eslint-disable-next-line */
export interface TargetProps { export interface TargetProps {
@ -24,16 +27,61 @@ export interface TargetProps {
sourceMap: Record<string, string[]>; sourceMap: Record<string, string[]>;
} }
export function Target(props: TargetProps) { export function Target({
const { environment } = useEnvironmentConfig(); projectName,
targetName,
targetConfiguration,
sourceMap,
}: TargetProps) {
const environment = useEnvironmentConfig()?.environment;
const externalApiService = getExternalApiService(); const externalApiService = getExternalApiService();
const navigate = useNavigate(); const navigate = useNavigate();
const routeContructor = useRouteConstructor(); const routeContructor = useRouteConstructor();
const [searchParams, setSearchParams] = useSearchParams();
const [collapsed, setCollapsed] = useState(true);
useEffect(() => {
const expandedSections = searchParams.get('expanded')?.split(',') || [];
setCollapsed(!expandedSections.includes(targetName));
}, [searchParams, targetName]);
function toggleCollapsed() {
setCollapsed((prevState) => {
const newState = !prevState;
setSearchParams((currentSearchParams) => {
const expandedSections =
currentSearchParams.get('expanded')?.split(',') || [];
if (newState) {
const newExpandedSections = expandedSections.filter(
(section) => section !== targetName
);
updateSearchParams(currentSearchParams, newExpandedSections);
} else {
if (!expandedSections.includes(targetName)) {
expandedSections.push(targetName);
updateSearchParams(currentSearchParams, expandedSections);
}
}
return currentSearchParams;
});
return newState;
});
}
function updateSearchParams(params: URLSearchParams, sections: string[]) {
if (sections.length === 0) {
params.delete('expanded');
} else {
params.set('expanded', sections.join(','));
}
}
const runTarget = () => { const runTarget = () => {
externalApiService.postEvent({ externalApiService.postEvent({
type: 'run-task', type: 'run-task',
payload: { taskId: `${props.projectName}:${props.targetName}` }, payload: { taskId: `${projectName}:${targetName}` },
}); });
}; };
@ -42,16 +90,16 @@ export function Target(props: TargetProps) {
externalApiService.postEvent({ externalApiService.postEvent({
type: 'open-task-graph', type: 'open-task-graph',
payload: { payload: {
projectName: props.projectName, projectName: projectName,
targetName: props.targetName, targetName: targetName,
}, },
}); });
} else { } else {
navigate( navigate(
routeContructor( routeContructor(
{ {
pathname: `/tasks/${encodeURIComponent(props.targetName)}`, pathname: `/tasks/${encodeURIComponent(targetName)}`,
search: `?projects=${encodeURIComponent(props.projectName)}`, search: `?projects=${encodeURIComponent(projectName)}`,
}, },
true true
) )
@ -59,73 +107,163 @@ export function Target(props: TargetProps) {
} }
}; };
const overrideTarget = () => { const shouldRenderOptions =
externalApiService.postEvent({ targetConfiguration.options &&
type: 'override-target', (typeof targetConfiguration.options === 'object'
payload: { ? Object.keys(targetConfiguration.options).length
projectName: props.projectName, : true);
targetName: props.targetName,
targetConfigString: JSON.stringify(props.targetConfiguration),
},
});
};
const shouldDisplayOverrideTarget = () => { const shouldRenderConfigurations =
return ( targetConfiguration.configurations &&
environment === 'nx-console' && (typeof targetConfiguration.configurations === 'object'
Object.entries(props.sourceMap ?? {}) ? Object.keys(targetConfiguration.configurations).length
.filter(([key]) => key.startsWith(`targets.${props.targetName}`)) : true);
.every(([, value]) => value[1] !== 'nx-core-build-project-json-nodes')
);
};
const targetConfigurationSortedAndFiltered = Object.entries(
props.targetConfiguration
)
.filter(([, value]) => {
return (
value &&
(Array.isArray(value) ? value.length : true) &&
(typeof value === 'object' ? Object.keys(value).length : true)
);
})
.sort(([a], [b]) => {
const order = ['executor', 'inputs', 'outputs'];
const indexA = order.indexOf(a);
const indexB = order.indexOf(b);
if (indexA !== -1 && indexB !== -1) {
return indexA - indexB;
} else if (indexA !== -1) {
return -1;
} else if (indexB !== -1) {
return 1;
} else {
return a.localeCompare(b);
}
});
return ( return (
<div className="ml-3 mb-3"> <div className="ml-3 mb-3 rounded-md border border-slate-500 relative overflow-hidden">
<h3 className="text-lg font-bold flex items-center gap-2"> {/* header */}
{props.targetName}{' '} <div className="group hover:bg-slate-800 px-2 cursor-pointer ">
{environment === 'nx-console' && ( <h3
<PlayIcon className="h-5 w-5" onClick={runTarget} /> className="text-lg font-bold flex items-center gap-2"
)} onClick={toggleCollapsed}
<EyeIcon className="h-5 w-5" onClick={viewInTaskGraph}></EyeIcon> >
{shouldDisplayOverrideTarget() && ( {targetName}{' '}
<PencilSquareIcon className="h-5 w-5" onClick={overrideTarget} /> <h4 className="text-sm text-slate-600">
)} {targetConfiguration?.command ??
</h3> targetConfiguration.options?.command ??
<div className="ml-3"> targetConfiguration.executor}
{targetConfigurationSortedAndFiltered.map(([key, value]) => </h4>
PropertyRenderer({ <span
propertyKey: key, className={
propertyValue: value, collapsed ? 'hidden group-hover:inline-flex' : 'inline-flex'
keyPrefix: `targets.${props.targetName}`, }
sourceMap: props.sourceMap, >
}) <span
)} className={`inline-flex justify-center rounded-md p-1 hover:bg-slate-100 hover:dark:bg-slate-700
}`}
>
<EyeIcon
className="h-4 w-4"
onClick={(e) => {
e.stopPropagation();
viewInTaskGraph();
}}
></EyeIcon>
</span>
{environment === 'nx-console' && (
<span
className={`inline-flex justify-center rounded-md p-1 hover:bg-slate-100 hover:dark:bg-slate-700
}`}
>
<PlayIcon
className="h-4 w-4"
onClick={(e) => {
e.stopPropagation();
runTarget();
}}
/>
</span>
)}
</span>
{targetConfiguration.cache && (
<span className="rounded-full inline-block text-xs bg-sky-500 px-2 text-slate-50 ml-auto mr-6">
Cacheable
</span>
)}
</h3>
<div className="absolute top-2 right-3" onClick={toggleCollapsed}>
{collapsed ? (
<ChevronUpIcon className="h-3 w-3" />
) : (
<ChevronDownIcon className="h-3 w-3" />
)}
</div>
</div> </div>
{/* body */}
{!collapsed && (
<div className="pl-5 text-base pb-6 pt-2 ">
{targetConfiguration.inputs && (
<>
<h4 className="font-bold">Inputs</h4>
<ul className="list-disc pl-5">
{targetConfiguration.inputs.map((input) => (
<li> {input.toString()} </li>
))}
</ul>
</>
)}
{targetConfiguration.outputs && (
<>
<h4 className="font-bold pt-2">Outputs</h4>
<ul className="list-disc pl-5">
{targetConfiguration.outputs?.map((output) => (
<li> {output.toString()} </li>
)) ?? <span>no outputs</span>}
</ul>
</>
)}
{targetConfiguration.dependsOn && (
<>
<h4 className="font-bold py-2">Depends On</h4>
<ul className="list-disc pl-5">
{targetConfiguration.dependsOn.map((dep) => (
<li> {dep.toString()} </li>
))}
</ul>
</>
)}
{shouldRenderOptions ? (
<>
<h4 className="font-bold py-2">Options</h4>
<FadingCollapsible>
<Fence
language="json"
command=""
path=""
fileName=""
highlightLines={[]}
lineGroups={{}}
enableCopy={true}
>
{JSON.stringify(targetConfiguration.options, null, 2)}
</Fence>
</FadingCollapsible>
</>
) : (
''
)}
{shouldRenderConfigurations ? (
<>
<h4 className="font-bold py-2">
Configurations{' '}
{targetConfiguration.defaultConfiguration && (
<span
className="ml-3 rounded-full inline-block text-xs bg-sky-500 px-2 text-slate-50 mr-6"
title="Default Configuration"
>
{targetConfiguration.defaultConfiguration}
</span>
)}
</h4>
<FadingCollapsible>
<Fence
language="json"
command=""
path=""
fileName=""
highlightLines={[]}
lineGroups={{}}
enableCopy={true}
>
{JSON.stringify(targetConfiguration.configurations, null, 2)}
</Fence>
</FadingCollapsible>
</>
) : (
''
)}
</div>
)}
</div> </div>
); );
} }

View File

@ -0,0 +1,55 @@
import { ArrowDownIcon, ArrowUpIcon } from '@heroicons/react/24/outline';
import { ReactNode, useEffect, useRef, useState } from 'react';
export function FadingCollapsible({ children }: { children: ReactNode }) {
const [collapsed, setCollapsed] = useState(true);
const [isCollapsible, setIsCollapsible] = useState(true);
const contentRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (contentRef.current) {
setIsCollapsible(contentRef.current.offsetHeight > 300);
}
}, [contentRef, children]);
function toggleCollapsed() {
setCollapsed(!collapsed);
}
const fadeStyles =
collapsed && isCollapsible
? {
maxHeight: '150px',
maskImage: 'linear-gradient(to bottom, black 40%, transparent)',
WebkitMaskImage: 'linear-gradient(to bottom, black 40%, transparent)',
}
: {};
return (
<div
className={`relative overflow-hidden ${
collapsed && isCollapsible ? 'cursor-pointer' : 'max-h-full'
}`}
onClick={() => collapsed && isCollapsible && toggleCollapsed()}
>
<div
className={`${
collapsed && isCollapsible ? 'hover:bg-slate-700' : ''
} rounded-md`}
style={fadeStyles}
>
<div ref={contentRef}>{children}</div>
</div>
{isCollapsible && (
<div
className="h-4 w-4 absolute bottom-2 right-1/2 cursor-pointer"
onClick={(e) => {
e.stopPropagation();
toggleCollapsed();
}}
>
{collapsed ? <ArrowDownIcon /> : <ArrowUpIcon />}
</div>
)}
</div>
);
}

View File

@ -1,21 +0,0 @@
import { useState, useCallback } from 'react';
function useMapState<K, V>(initialMap: Map<K, V> = new Map()) {
const [map, setMap] = useState(new Map(initialMap));
// Function to set a key-value pair in the map
const setKey = useCallback((key: K, value: V) => {
setMap((prevMap) => {
const newMap = new Map(prevMap);
newMap.set(key, value);
return newMap;
});
}, []);
// Function to get a value by key from the map
const getKey = useCallback((key: K) => map.get(key), [map]);
return [getKey, setKey] as const;
}
export default useMapState;

View File

@ -6,7 +6,8 @@
"node", "node",
"@nx/react/typings/cssmodule.d.ts", "@nx/react/typings/cssmodule.d.ts",
"@nx/react/typings/image.d.ts" "@nx/react/typings/image.d.ts"
] ],
"lib": ["DOM"]
}, },
"exclude": [ "exclude": [
"jest.config.ts", "jest.config.ts",

View File

@ -0,0 +1,12 @@
{
"presets": [
[
"@nx/react/babel",
{
"runtime": "automatic",
"useBuiltIns": "usage"
}
]
],
"plugins": []
}

View File

@ -0,0 +1,18 @@
{
"extends": ["plugin:@nx/react", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}

View File

@ -0,0 +1,7 @@
# shared-ui-fence
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test shared-ui-fence` to execute the unit tests via [Jest](https://jestjs.io).

View File

@ -0,0 +1,10 @@
/* eslint-disable */
export default {
displayName: 'shared-ui-fence',
preset: '../../jest.preset.js',
transform: {
'^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nx/react/babel'] }],
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageDirectory: '../../coverage/nx-dev/shared-ui-fence',
};

View File

@ -0,0 +1,20 @@
{
"name": "shared-ui-fence",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "nx-dev/shared-ui-fence/src",
"projectType": "library",
"tags": [],
"targets": {
"lint": {
"executor": "@nx/eslint:lint"
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "nx-dev/shared-ui-fence/jest.config.ts",
"passWithNoTests": true
}
}
}
}

View File

@ -0,0 +1,3 @@
export * from './lib/fence.component';
export { TerminalOutput } from './lib/fences/terminal-output.component';
export { TerminalShellWrapper } from './lib/fences/terminal-shell.component';

View File

@ -11,8 +11,7 @@ import { CopyToClipboard } from 'react-copy-to-clipboard';
import SyntaxHighlighter from 'react-syntax-highlighter'; import SyntaxHighlighter from 'react-syntax-highlighter';
import { CodeOutput } from './fences/code-output.component'; import { CodeOutput } from './fences/code-output.component';
import { TerminalOutput } from './fences/terminal-output.component'; import { TerminalOutput } from './fences/terminal-output.component';
import { useRouter } from 'next/router'; import { Selector } from '@nx/shared-ui-selector';
import { Selector } from '@nx/nx-dev/ui-common';
function resolveLanguage(lang: string) { function resolveLanguage(lang: string) {
switch (lang) { switch (lang) {
@ -57,32 +56,6 @@ function CodeWrapper(options: {
); );
} }
const useUrlHash = (initialValue: string) => {
const router = useRouter();
const [hash, setHash] = useState(initialValue);
const updateHash = (str: string) => {
if (!str) return;
setHash(str.split('#')[1]);
};
useEffect(() => {
const onWindowHashChange = () => updateHash(window.location.hash);
const onNextJSHashChange = (url: string) => updateHash(url);
router.events.on('hashChangeStart', onNextJSHashChange);
window.addEventListener('hashchange', onWindowHashChange);
window.addEventListener('load', onWindowHashChange);
return () => {
router.events.off('hashChangeStart', onNextJSHashChange);
window.removeEventListener('load', onWindowHashChange);
window.removeEventListener('hashchange', onWindowHashChange);
};
}, [router.asPath, router.events]);
return hash;
};
// pre-process the highlightLines to expand ranges like // pre-process the highlightLines to expand ranges like
// ["8-10", 19, 22] => [8,9,10,19,22] // ["8-10", 19, 22] => [8,9,10,19,22]
function processHighlightLines(highlightLines: any): number[] { function processHighlightLines(highlightLines: any): number[] {
@ -109,6 +82,19 @@ function processHighlightLines(highlightLines: any): number[] {
); );
} }
export interface FenceProps {
children: string;
command: string;
path: string;
fileName: string;
highlightLines: number[];
lineGroups: Record<string, number[]>;
language: string;
enableCopy: boolean;
selectedLineGroup?: string;
onLineGroupSelectionChange?: (selection: string) => void;
}
export function Fence({ export function Fence({
children, children,
command, command,
@ -118,19 +104,9 @@ export function Fence({
highlightLines, highlightLines,
language, language,
enableCopy, enableCopy,
}: { selectedLineGroup,
children: string; onLineGroupSelectionChange,
command: string; }: FenceProps) {
path: string;
fileName: string;
highlightLines: number[];
lineGroups: Record<string, number[]>;
language: string;
enableCopy: boolean;
}) {
const { push, asPath } = useRouter();
const hash = decodeURIComponent(useUrlHash(''));
if (highlightLines) { if (highlightLines) {
highlightLines = processHighlightLines(highlightLines); highlightLines = processHighlightLines(highlightLines);
} }
@ -138,7 +114,9 @@ export function Fence({
function lineNumberStyle(lineNumber: number) { function lineNumberStyle(lineNumber: number) {
if ( if (
(highlightLines && highlightLines.includes(lineNumber)) || (highlightLines && highlightLines.includes(lineNumber)) ||
(lineGroups[hash] && lineGroups[hash].includes(lineNumber)) (selectedLineGroup &&
lineGroups[selectedLineGroup] &&
lineGroups[selectedLineGroup].includes(lineNumber))
) { ) {
return { return {
fontSize: 0, fontSize: 0,
@ -168,7 +146,7 @@ export function Fence({
}); });
} }
let selectedOption = let selectedOption =
highlightOptions.find((option) => option.value === hash) || highlightOptions.find((option) => option.value === selectedLineGroup) ||
highlightOptions[0]; highlightOptions[0];
const [copied, setCopied] = useState(false); const [copied, setCopied] = useState(false);
useEffect(() => { useEffect(() => {
@ -185,77 +163,75 @@ export function Fence({
const showRescopeMessage = const showRescopeMessage =
children.includes('@nx/') || command.includes('@nx/'); children.includes('@nx/') || command.includes('@nx/');
function highlightChange(item: { label: string; value: string }) { function highlightChange(item: { label: string; value: string }) {
push(asPath.split('#')[0] + '#' + item.value); onLineGroupSelectionChange?.(item.value);
} }
return ( return (
<div className="my-8 w-full"> <div className="code-block group relative w-full">
<div className="code-block group relative w-full"> <div>
<div> <div className="absolute top-0 right-0 z-10 flex">
<div className="absolute top-0 right-0 z-10 flex"> {enableCopy && enableCopy === true && (
{enableCopy && enableCopy === true && ( <CopyToClipboard
<CopyToClipboard text={command && command !== '' ? command : children}
text={command && command !== '' ? command : children} onCopy={() => {
onCopy={() => { setCopied(true);
setCopied(true); }}
}}
>
<button
type="button"
className={
'opacity-0 transition-opacity group-hover:opacity-100 not-prose flex border border-slate-200 bg-slate-50/50 p-2 dark:border-slate-700 dark:bg-slate-800/60' +
(highlightOptions && highlightOptions[0]
? ''
: ' rounded-tr-lg')
}
>
{copied ? (
<ClipboardDocumentCheckIcon className="h-5 w-5 text-blue-500 dark:text-sky-500" />
) : (
<ClipboardDocumentIcon className="h-5 w-5" />
)}
</button>
</CopyToClipboard>
)}
{highlightOptions && highlightOptions[0] && (
<Selector
className="rounded-tr-lg"
items={highlightOptions}
selected={selectedOption}
onChange={highlightChange}
>
<SparklesIcon className="h-5 w-5 mr-1"></SparklesIcon>
</Selector>
)}
</div>
<SyntaxHighlighter
useInlineStyles={false}
showLineNumbers={true}
lineNumberStyle={lineNumberStyle}
language={resolveLanguage(language)}
children={children}
PreTag={CodeWrapper({
fileName,
command,
path,
isMessageBelow: showRescopeMessage,
language,
children,
})}
/>
{showRescopeMessage && (
<a
className="relative block rounded-b-md border border-slate-200 bg-slate-50 px-4 py-2 text-xs font-medium no-underline hover:underline dark:border-slate-700 dark:bg-slate-800"
href="/recipes/other/rescope"
title="Nx 16 package name changes"
> >
<InformationCircleIcon <button
className="mr-2 inline-block h-5 w-5" type="button"
aria-hidden="true" className={
/> 'opacity-0 transition-opacity group-hover:opacity-100 not-prose flex border border-slate-200 bg-slate-50/50 p-2 dark:border-slate-700 dark:bg-slate-800/60' +
Nx 15 and lower use @nrwl/ instead of @nx/ (highlightOptions && highlightOptions[0]
</a> ? ''
: ' rounded-tr-lg')
}
>
{copied ? (
<ClipboardDocumentCheckIcon className="h-5 w-5 text-blue-500 dark:text-sky-500" />
) : (
<ClipboardDocumentIcon className="h-5 w-5" />
)}
</button>
</CopyToClipboard>
)}
{highlightOptions && highlightOptions[0] && (
<Selector
className="rounded-tr-lg"
items={highlightOptions}
selected={selectedOption}
onChange={highlightChange}
>
<SparklesIcon className="h-5 w-5 mr-1"></SparklesIcon>
</Selector>
)} )}
</div> </div>
<SyntaxHighlighter
useInlineStyles={false}
showLineNumbers={true}
lineNumberStyle={lineNumberStyle}
language={resolveLanguage(language)}
children={children}
PreTag={CodeWrapper({
fileName,
command,
path,
isMessageBelow: showRescopeMessage,
language,
children,
})}
/>
{showRescopeMessage && (
<a
className="relative block rounded-b-md border border-slate-200 bg-slate-50 px-4 py-2 text-xs font-medium no-underline hover:underline dark:border-slate-700 dark:bg-slate-800"
href="/recipes/other/rescope"
title="Nx 16 package name changes"
>
<InformationCircleIcon
className="mr-2 inline-block h-5 w-5"
aria-hidden="true"
/>
Nx 15 and lower use @nrwl/ instead of @nx/
</a>
)}
</div> </div>
</div> </div>
); );

View File

@ -0,0 +1,20 @@
{
"compilerOptions": {
"jsx": "react-jsx",
"allowJs": false,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
],
"extends": "../../tsconfig.base.json"
}

View File

@ -0,0 +1,24 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"types": [
"node",
"@nx/react/typings/cssmodule.d.ts",
"@nx/react/typings/image.d.ts"
],
"lib": ["DOM", "es2019"]
},
"exclude": [
"jest.config.ts",
"src/**/*.spec.ts",
"src/**/*.test.ts",
"src/**/*.spec.tsx",
"src/**/*.test.tsx",
"src/**/*.spec.js",
"src/**/*.test.js",
"src/**/*.spec.jsx",
"src/**/*.test.jsx"
],
"include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"]
}

View File

@ -0,0 +1,20 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"]
},
"include": [
"jest.config.ts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.test.tsx",
"src/**/*.spec.tsx",
"src/**/*.test.js",
"src/**/*.spec.js",
"src/**/*.test.jsx",
"src/**/*.spec.jsx",
"src/**/*.d.ts"
]
}

View File

@ -0,0 +1,12 @@
{
"presets": [
[
"@nx/react/babel",
{
"runtime": "automatic",
"useBuiltIns": "usage"
}
]
],
"plugins": []
}

View File

@ -0,0 +1,18 @@
{
"extends": ["plugin:@nx/react", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}

View File

@ -0,0 +1,7 @@
# shared-ui-selector
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test shared-ui-selector` to execute the unit tests via [Jest](https://jestjs.io).

View File

@ -0,0 +1,10 @@
/* eslint-disable */
export default {
displayName: 'shared-ui-selector',
preset: '../../jest.preset.js',
transform: {
'^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nx/react/babel'] }],
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageDirectory: '../../coverage/nx-dev/shared-ui-selector',
};

View File

@ -0,0 +1,20 @@
{
"name": "shared-ui-selector",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "nx-dev/shared-ui-selector/src",
"projectType": "library",
"tags": [],
"targets": {
"lint": {
"executor": "@nx/eslint:lint"
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "nx-dev/shared-ui-selector/jest.config.ts",
"passWithNoTests": true
}
}
}
}

View File

@ -0,0 +1 @@
export * from './lib/selector';

View File

@ -0,0 +1,20 @@
{
"compilerOptions": {
"jsx": "react-jsx",
"allowJs": false,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
],
"extends": "../../tsconfig.base.json"
}

View File

@ -0,0 +1,24 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"types": [
"node",
"@nx/react/typings/cssmodule.d.ts",
"@nx/react/typings/image.d.ts"
]
},
"exclude": [
"jest.config.ts",
"src/**/*.spec.ts",
"src/**/*.test.ts",
"src/**/*.spec.tsx",
"src/**/*.test.tsx",
"src/**/*.spec.js",
"src/**/*.test.js",
"src/**/*.spec.jsx",
"src/**/*.test.jsx"
],
"include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"]
}

View File

@ -0,0 +1,20 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"]
},
"include": [
"jest.config.ts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.test.tsx",
"src/**/*.spec.tsx",
"src/**/*.test.js",
"src/**/*.spec.js",
"src/**/*.test.jsx",
"src/**/*.spec.jsx",
"src/**/*.d.ts"
]
}

View File

@ -1,6 +1,7 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
// @ts-ignore // @ts-ignore
import { CopyToClipboard } from 'react-copy-to-clipboard'; import { CopyToClipboard } from 'react-copy-to-clipboard';
// @ts-ignore
import SyntaxHighlighter from 'react-syntax-highlighter'; import SyntaxHighlighter from 'react-syntax-highlighter';
/* eslint-disable-next-line */ /* eslint-disable-next-line */

View File

@ -7,7 +7,6 @@ export * from './lib/champion-perks';
export * from './lib/header'; export * from './lib/header';
export * from './lib/flip-card'; export * from './lib/flip-card';
export * from './lib/footer'; export * from './lib/footer';
export * from './lib/selector';
export * from './lib/sidebar-container'; export * from './lib/sidebar-container';
export * from './lib/sidebar'; export * from './lib/sidebar';
export * from './lib/nx-users-showcase'; export * from './lib/nx-users-showcase';

View File

@ -8,8 +8,6 @@ import {
} from '@markdoc/markdoc'; } from '@markdoc/markdoc';
import { load as yamlLoad } from 'js-yaml'; import { load as yamlLoad } from 'js-yaml';
import React, { ReactNode } from 'react'; import React, { ReactNode } from 'react';
import { Fence } from './lib/nodes/fence.component';
import { fence } from './lib/nodes/fence.schema';
import { Heading } from './lib/nodes/heading.component'; import { Heading } from './lib/nodes/heading.component';
import { heading } from './lib/nodes/heading.schema'; import { heading } from './lib/nodes/heading.schema';
import { getImageSchema } from './lib/nodes/image.schema'; import { getImageSchema } from './lib/nodes/image.schema';
@ -55,6 +53,8 @@ import { VideoLink, videoLink } from './lib/tags/video-link.component';
import { Pill } from './lib/tags/pill.component'; import { Pill } from './lib/tags/pill.component';
import { pill } from './lib/tags/pill.schema'; import { pill } from './lib/tags/pill.schema';
import { frameworkIcons } from './lib/icons'; import { frameworkIcons } from './lib/icons';
import { fence } from './lib/nodes/fence.schema';
import { FenceWrapper } from './lib/nodes/fence-wrapper.component';
// TODO fix this export // TODO fix this export
export { GithubRepository } from './lib/tags/github-repository.component'; export { GithubRepository } from './lib/tags/github-repository.component';
@ -103,7 +103,7 @@ export const getMarkdocCustomConfig = (
Disclosure, Disclosure,
LinkCard, LinkCard,
CustomLink, CustomLink,
Fence, FenceWrapper,
GithubRepository, GithubRepository,
StackblitzButton, StackblitzButton,
Graph, Graph,

View File

@ -0,0 +1,47 @@
import { Fence, FenceProps } from '@nx/shared-ui-fence';
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
const useUrlHash = (initialValue: string) => {
const router = useRouter();
const [hash, setHash] = useState(initialValue);
const updateHash = (str: string) => {
if (!str) return;
setHash(str.split('#')[1]);
};
useEffect(() => {
const onWindowHashChange = () => updateHash(window.location.hash);
const onNextJSHashChange = (url: string) => updateHash(url);
router.events.on('hashChangeStart', onNextJSHashChange);
window.addEventListener('hashchange', onWindowHashChange);
window.addEventListener('load', onWindowHashChange);
return () => {
router.events.off('hashChangeStart', onNextJSHashChange);
window.removeEventListener('load', onWindowHashChange);
window.removeEventListener('hashchange', onWindowHashChange);
};
}, [router.asPath, router.events]);
return hash;
};
export function FenceWrapper(props: FenceProps) {
const { push, asPath } = useRouter();
const hash = decodeURIComponent(useUrlHash(''));
const modifiedProps: FenceProps = {
...props,
selectedLineGroup: hash,
onLineGroupSelectionChange: (selection: string) => {
push(asPath.split('#')[0] + '#' + selection);
},
};
return (
<div className="my-8 w-full">
<Fence {...modifiedProps} />
</div>
);
}

View File

@ -1,7 +1,7 @@
import { Schema, Tag } from '@markdoc/markdoc'; import { Schema, Tag } from '@markdoc/markdoc';
export const fence: Schema = { export const fence: Schema = {
render: 'Fence', render: 'FenceWrapper',
attributes: { attributes: {
content: { type: 'String', render: false, required: true }, content: { type: 'String', render: false, required: true },
language: { type: 'String' }, language: { type: 'String' },
@ -18,6 +18,6 @@ export const fence: Schema = {
const children = node.children.length const children = node.children.length
? node.transformChildren(config) ? node.transformChildren(config)
: [node.attributes['content']]; : [node.attributes['content']];
return new Tag('Fence', attributes, children); return new Tag('FenceWrapper', attributes, children);
}, },
}; };

View File

@ -1,6 +1,6 @@
import { TerminalShellWrapper } from '@nx/shared-ui-fence';
import { VideoLoop } from './video-loop.component'; import { VideoLoop } from './video-loop.component';
import { Schema } from '@markdoc/markdoc'; import { Schema } from '@markdoc/markdoc';
import { TerminalShellWrapper } from '../nodes/fences/terminal-shell.component';
export const terminalVideo: Schema = { export const terminalVideo: Schema = {
render: 'TerminalVideo', render: 'TerminalVideo',

View File

@ -106,7 +106,7 @@
"@swc/cli": "0.1.62", "@swc/cli": "0.1.62",
"@swc/core": "^1.3.85", "@swc/core": "^1.3.85",
"@swc/jest": "^0.2.20", "@swc/jest": "^0.2.20",
"@testing-library/react": "13.4.0", "@testing-library/react": "14.0.0",
"@types/cytoscape": "^3.18.2", "@types/cytoscape": "^3.18.2",
"@types/detect-port": "^1.3.2", "@types/detect-port": "^1.3.2",
"@types/ejs": "3.1.2", "@types/ejs": "3.1.2",
@ -123,8 +123,8 @@
"@types/node": "18.16.9", "@types/node": "18.16.9",
"@types/npm-package-arg": "6.1.1", "@types/npm-package-arg": "6.1.1",
"@types/prettier": "^2.6.2", "@types/prettier": "^2.6.2",
"@types/react": "18.2.24", "@types/react": "18.2.33",
"@types/react-dom": "18.2.9", "@types/react-dom": "18.2.14",
"@types/semver": "^7.5.2", "@types/semver": "^7.5.2",
"@types/tar-stream": "^2.2.2", "@types/tar-stream": "^2.2.2",
"@types/tmp": "^0.2.0", "@types/tmp": "^0.2.0",
@ -169,10 +169,10 @@
"eslint-config-next": "13.1.1", "eslint-config-next": "13.1.1",
"eslint-config-prettier": "9.0.0", "eslint-config-prettier": "9.0.0",
"eslint-plugin-cypress": "2.14.0", "eslint-plugin-cypress": "2.14.0",
"eslint-plugin-import": "2.26.0", "eslint-plugin-import": "2.27.5",
"eslint-plugin-jsx-a11y": "6.6.1", "eslint-plugin-jsx-a11y": "6.7.1",
"eslint-plugin-playwright": "^0.15.3", "eslint-plugin-playwright": "^0.15.3",
"eslint-plugin-react": "7.31.11", "eslint-plugin-react": "7.32.2",
"eslint-plugin-react-hooks": "4.6.0", "eslint-plugin-react-hooks": "4.6.0",
"eslint-plugin-storybook": "^0.6.12", "eslint-plugin-storybook": "^0.6.12",
"express": "^4.18.1", "express": "^4.18.1",
@ -249,7 +249,7 @@
"react-markdown": "^8.0.7", "react-markdown": "^8.0.7",
"react-redux": "8.0.5", "react-redux": "8.0.5",
"react-refresh": "^0.10.0", "react-refresh": "^0.10.0",
"react-router-dom": "^6.11.2", "react-router-dom": "^6.21.2",
"react-textarea-autosize": "^8.5.3", "react-textarea-autosize": "^8.5.3",
"regenerator-runtime": "0.13.7", "regenerator-runtime": "0.13.7",
"resolve.exports": "1.1.0", "resolve.exports": "1.1.0",
@ -371,4 +371,3 @@
] ]
} }
} }

View File

@ -9,7 +9,12 @@ describe('createWatchPaths', () => {
const testDir = joinPathFragments(workspaceRoot, 'e2e/remix'); const testDir = joinPathFragments(workspaceRoot, 'e2e/remix');
const paths = await createWatchPaths(testDir); const paths = await createWatchPaths(testDir);
expect(paths).toEqual(['../../packages', '../../graph', '../../e2e/utils']); expect(paths).toEqual([
'../../packages',
'../../graph',
'../../nx-dev',
'../../e2e/utils',
]);
}); });
}); });

1849
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -102,6 +102,8 @@
"@nx/remix/*": ["packages/remix/*"], "@nx/remix/*": ["packages/remix/*"],
"@nx/rollup": ["packages/rollup"], "@nx/rollup": ["packages/rollup"],
"@nx/rollup/*": ["packages/rollup/*"], "@nx/rollup/*": ["packages/rollup/*"],
"@nx/shared-ui-fence": ["nx-dev/shared-ui-fence/src/index.ts"],
"@nx/shared-ui-selector": ["nx-dev/shared-ui-selector/src/index.ts"],
"@nx/storybook": ["packages/storybook"], "@nx/storybook": ["packages/storybook"],
"@nx/storybook/*": ["packages/storybook/*"], "@nx/storybook/*": ["packages/storybook/*"],
"@nx/typedoc-theme": ["typedoc-theme/src/index.ts"], "@nx/typedoc-theme": ["typedoc-theme/src/index.ts"],