feat(graph): add project details view (#20466)

Co-authored-by: FrozenPandaz <jasonjean1993@gmail.com>
This commit is contained in:
MaxKless 2023-12-04 15:53:19 +01:00 committed by GitHub
parent a08fdf0e36
commit 75cc561e9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 4115 additions and 93 deletions

1
.gitignore vendored
View File

@ -19,6 +19,7 @@ jest.debug.config.js
/graph/client/src/assets/generated-project-graphs /graph/client/src/assets/generated-project-graphs
/graph/client/src/assets/generated-task-graphs /graph/client/src/assets/generated-task-graphs
/graph/client/src/assets/generated-task-inputs /graph/client/src/assets/generated-task-inputs
/graph/client/src/assets/generated-source-maps
/nx-dev/nx-dev/public/documentation /nx-dev/nx-dev/public/documentation
/nx-dev/nx-dev/public/images/open-graph /nx-dev/nx-dev/public/images/open-graph

View File

@ -68,6 +68,7 @@
"graph/client/src/assets/generated-project-graphs/", "graph/client/src/assets/generated-project-graphs/",
"graph/client/src/assets/generated-task-graphs/", "graph/client/src/assets/generated-task-graphs/",
"graph/client/src/assets/generated-task-inputs/", "graph/client/src/assets/generated-task-inputs/",
"graph/client/src/assets/generated-source-maps/",
{ {
"input": "graph/client/src/assets/dev", "input": "graph/client/src/assets/dev",
"output": "/", "output": "/",
@ -81,6 +82,7 @@
"graph/client/src/assets/project-graphs/", "graph/client/src/assets/project-graphs/",
"graph/client/src/assets/task-graphs/", "graph/client/src/assets/task-graphs/",
"graph/client/src/assets/task-inputs/", "graph/client/src/assets/task-inputs/",
"graph/client/src/assets/source-maps/",
{ {
"input": "graph/client/src/assets/dev-e2e", "input": "graph/client/src/assets/dev-e2e",
"output": "/", "output": "/",
@ -116,6 +118,11 @@
"output": "/assets/task-graphs", "output": "/assets/task-graphs",
"glob": "e2e.json" "glob": "e2e.json"
}, },
{
"input": "graph/client/src/assets/source-maps",
"output": "/assets/source-maps",
"glob": "e2e.json"
},
{ {
"input": "graph/client/src/assets/release", "input": "graph/client/src/assets/release",
"output": "/", "output": "/",

View File

@ -34,6 +34,16 @@ export class FetchProjectGraphService implements ProjectGraphService {
return response.json(); return response.json();
} }
async getSourceMaps(
url: string
): Promise<Record<string, Record<string, string[]>>> {
const request = new Request(url, { mode: 'no-cors' });
const response = await fetch(request);
return response.json();
}
setTaskInputsUrl(url: string) { setTaskInputsUrl(url: string) {
this.taskInputsUrl = url; this.taskInputsUrl = url;
} }

View File

@ -12,6 +12,7 @@ export interface WorkspaceData {
projectGraphUrl: string; projectGraphUrl: string;
taskGraphUrl: string; taskGraphUrl: string;
taskInputsUrl: string; taskInputsUrl: string;
sourceMapsUrl: string;
} }
export interface WorkspaceLayout { export interface WorkspaceLayout {
@ -25,6 +26,9 @@ export interface ProjectGraphService {
getTaskGraph: (url: string) => Promise<TaskGraphClientResponse>; getTaskGraph: (url: string) => Promise<TaskGraphClientResponse>;
setTaskInputsUrl?: (url: string) => void; setTaskInputsUrl?: (url: string) => void;
getExpandedTaskInputs?: (taskId: string) => Promise<Record<string, string[]>>; getExpandedTaskInputs?: (taskId: string) => Promise<Record<string, string[]>>;
getSourceMaps?: (
url: string
) => Promise<Record<string, Record<string, string[]>>>;
} }
export interface Environment { export interface Environment {

View File

@ -27,4 +27,10 @@ export class LocalProjectGraphService implements ProjectGraphService {
resolve(window.expandedTaskInputsResponse[taskId]) resolve(window.expandedTaskInputsResponse[taskId])
); );
} }
async getSourceMaps(
url: string
): Promise<Record<string, Record<string, string[]>>> {
return new Promise((resolve) => resolve(window.sourceMapsResponse));
}
} }

View File

@ -9,6 +9,7 @@ import { ProjectGraphClientResponse } from 'nx/src/command-line/graph/graph';
/* eslint-enable @nx/enforce-module-boundaries */ /* eslint-enable @nx/enforce-module-boundaries */
import { getProjectGraphDataService } from './hooks/get-project-graph-data-service'; import { getProjectGraphDataService } from './hooks/get-project-graph-data-service';
import { TasksSidebarErrorBoundary } from './feature-tasks/tasks-sidebar-error-boundary'; import { TasksSidebarErrorBoundary } from './feature-tasks/tasks-sidebar-error-boundary';
import { ProjectDetails } from '@nx/graph/project-details';
const { appConfig } = getEnvironmentConfig(); const { appConfig } = getEnvironmentConfig();
const projectGraphDataService = getProjectGraphDataService(); const projectGraphDataService = getProjectGraphDataService();
@ -47,11 +48,37 @@ const workspaceDataLoader = async (selectedWorkspaceId: string) => {
}; };
const taskDataLoader = async (selectedWorkspaceId: string) => { const taskDataLoader = async (selectedWorkspaceId: string) => {
const projectInfo = appConfig.workspaces.find( const workspaceInfo = appConfig.workspaces.find(
(graph) => graph.id === selectedWorkspaceId (graph) => graph.id === selectedWorkspaceId
); );
return await projectGraphDataService.getTaskGraph(projectInfo.taskGraphUrl); return await projectGraphDataService.getTaskGraph(workspaceInfo.taskGraphUrl);
};
const sourceMapsLoader = async (selectedWorkspaceId: string) => {
const workspaceInfo = appConfig.workspaces.find(
(graph) => graph.id === selectedWorkspaceId
);
return await projectGraphDataService.getSourceMaps(
workspaceInfo.sourceMapsUrl
);
};
const projectDetailsLoader = async (
selectedWorkspaceId: string,
projectName: string
) => {
const workspaceData = await workspaceDataLoader(selectedWorkspaceId);
const sourceMaps = await sourceMapsLoader(selectedWorkspaceId);
const project = workspaceData.projects.find(
(project) => project.name === projectName
);
return {
project,
sourceMap: sourceMaps[project.data.root],
};
}; };
const childRoutes: RouteObject[] = [ const childRoutes: RouteObject[] = [
@ -146,6 +173,15 @@ export const devRoutes: RouteObject[] = [
}, },
children: childRoutes, children: childRoutes,
}, },
{
path: ':selectedWorkspaceId/project-details/:projectName',
id: 'selectedProjectDetails',
element: <ProjectDetails />,
loader: async ({ request, params }) => {
const projectName = params.projectName;
return projectDetailsLoader(params.selectedWorkspaceId, projectName);
},
},
], ],
}, },
]; ];
@ -174,4 +210,13 @@ export const releaseRoutes: RouteObject[] = [
...childRoutes, ...childRoutes,
], ],
}, },
{
path: 'project-details/:projectName',
id: 'selectedProjectDetails',
element: <ProjectDetails />,
loader: async ({ request, params }) => {
const projectName = params.projectName;
return projectDetailsLoader(appConfig.defaultWorkspaceId, projectName);
},
},
]; ];

View File

@ -13,6 +13,7 @@ window.appConfig = {
projectGraphUrl: 'assets/project-graphs/e2e.json', projectGraphUrl: 'assets/project-graphs/e2e.json',
taskGraphUrl: 'assets/task-graphs/e2e.json', taskGraphUrl: 'assets/task-graphs/e2e.json',
taskInputsUrl: 'assets/task-inputs/e2e.json', taskInputsUrl: 'assets/task-inputs/e2e.json',
sourceMapsUrl: 'assets/source-maps/e2e.json',
}, },
], ],
defaultWorkspaceId: 'local', defaultWorkspaceId: 'local',

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -8,6 +8,9 @@ import type {
/* eslint-enable @nx/enforce-module-boundaries */ /* eslint-enable @nx/enforce-module-boundaries */
import { AppConfig } from './app/interfaces'; import { AppConfig } from './app/interfaces';
import { ExternalApi } from './app/external-api'; import { ExternalApi } from './app/external-api';
/* eslint-disable @nx/enforce-module-boundaries */
// nx-ignore-next-line
import { ConfigurationSourceMaps } from '../../project-graph/utils/project-configuration-utils';
export declare global { export declare global {
export interface Window { export interface Window {
@ -17,6 +20,7 @@ export declare global {
projectGraphResponse?: ProjectGraphClientResponse; projectGraphResponse?: ProjectGraphClientResponse;
taskGraphResponse?: TaskGraphClientResponse; taskGraphResponse?: TaskGraphClientResponse;
expandedTaskInputsResponse?: ExpandedTaskInputsReponse; expandedTaskInputsResponse?: ExpandedTaskInputsReponse;
sourceMapsResponse?: ConfigurationSourceMaps;
environment: 'dev' | 'watch' | 'release' | 'nx-console'; environment: 'dev' | 'watch' | 'release' | 'nx-console';
appConfig: AppConfig; appConfig: AppConfig;
useXstateInspect: boolean; useXstateInspect: boolean;

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 @@
# project-details
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test project-details` to execute the unit tests via [Jest](https://jestjs.io).

View File

@ -0,0 +1,16 @@
{
"name": "graph-project-details",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "graph/project-details/src",
"projectType": "library",
"tags": [],
"targets": {
"lint": {
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["graph/project-details/**/*.{ts,tsx,js,jsx}"]
}
}
}
}

View File

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

View File

@ -0,0 +1,16 @@
export function getSourceInformation(
sourceMap: Record<string, string[]>,
key: string
): string | undefined {
const sourceInfo = sourceMap?.[key];
if (sourceInfo) {
return `${key} was set by plugin \n \n ${sourceInfo[1]} \n \n while processing \n \n ${sourceInfo[0]}`;
}
if (!key.includes('.')) {
return undefined;
}
return getSourceInformation(
sourceMap,
key.substring(0, key.lastIndexOf('.'))
);
}

View File

@ -0,0 +1,66 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import styles from './app.module.css';
import Target from './target';
import PropertyRenderer from './property-renderer';
import { useRouteLoaderData } from 'react-router-dom';
/* eslint-disable @nx/enforce-module-boundaries */
// nx-ignore-next-line
import { ProjectGraphProjectNode } from '@nx/devkit';
export function ProjectDetails() {
const {
project: {
name,
data: { targets, root, ...projectData },
},
sourceMap,
} = useRouteLoaderData('selectedProjectDetails') as {
project: ProjectGraphProjectNode;
sourceMap: Record<string, string[]>;
};
return (
<div className="m-4 overflow-auto w-full">
<h1 className="text-2xl">{name}</h1>
<h2 className="text-lg pl-6 mb-3 flex flex-row gap-2">
{root}{' '}
{projectData.tags?.map((tag) => (
<p className="bg-slate-300">{tag}</p>
))}
</h2>
<div>
<div className="mb-2">
<h2 className="text-xl">Targets</h2>
{Object.entries(targets ?? {}).map(([targetName, target]) =>
Target({
targetName: targetName,
targetConfiguration: target,
sourceMap,
})
)}
</div>
{Object.entries(projectData).map(([key, value]) => {
if (
key === 'targets' ||
key === 'root' ||
key === 'name' ||
key === '$schema' ||
key === 'tags' ||
key === 'files'
)
return undefined;
return PropertyRenderer({
propertyKey: key,
propertyValue: value,
sourceMap,
});
})}
</div>
</div>
);
}
export default ProjectDetails;

View File

@ -0,0 +1,116 @@
import { getSourceInformation } from './get-source-information';
import { useState } from 'react';
/* eslint-disable-next-line */
export interface PropertyRendererProps {
propertyKey: string;
propertyValue: any;
keyPrefix?: string;
sourceMap: Record<string, string[]>;
}
export function PropertyRenderer(props: PropertyRendererProps) {
const { propertyValue, propertyKey, sourceMap, keyPrefix } = props;
const sourceMapKey = `${keyPrefix ? `${keyPrefix}.` : ''}${propertyKey}`;
const isCollapsible = propertyValue && typeof propertyValue === 'object';
const [isCollapsed, setIsCollapsed] = useState(true);
const toggleCollapse = () => {
setIsCollapsed(!isCollapsed);
};
return (
<div title={getSourceInformation(sourceMap, sourceMapKey)}>
{isCollapsible && (
<button className="text-xs" onClick={toggleCollapse}>
{isCollapsed ? '\u25B6' : '\u25BC'}
</button>
)}
<span className="font-medium">{propertyKey}</span>:{' '}
{renderOpening(propertyValue)}
{!isCollapsed || !isCollapsible ? (
<PropertyValueRenderer {...props} />
) : (
'...'
)}
{renderClosing(propertyValue)}
</div>
);
}
type PropertValueRendererProps = PropertyRendererProps & {
nested?: boolean;
};
function PropertyValueRenderer(props: PropertValueRendererProps) {
const { propertyKey, propertyValue, sourceMap, keyPrefix, nested } = props;
if (Array.isArray(propertyValue) && propertyValue.length) {
return (
<div className="ml-3">
{nested && renderOpening(propertyValue)}
{propertyValue.map((v) =>
PropertyValueRenderer({
propertyKey,
propertyValue: v,
sourceMap,
keyPrefix: `${keyPrefix ? `${keyPrefix}.` : ''}${v}`,
nested: true,
})
)}
{nested && renderClosing(propertyValue)}
</div>
);
} else if (propertyValue && typeof propertyValue === 'object') {
return (
<div
title={getSourceInformation(
sourceMap,
`${keyPrefix ? `${keyPrefix}.` : ''}${propertyKey}`
)}
>
{nested && renderOpening(propertyValue)}
<div className="ml-3">
{Object.entries(propertyValue)
.filter(
([, value]) =>
value && (Array.isArray(value) ? value.length : true)
)
.map(([key, value]) =>
PropertyRenderer({
propertyKey: key,
propertyValue: value,
keyPrefix: `${keyPrefix ? `${keyPrefix}.` : ''}${propertyKey}`,
sourceMap,
})
)}
</div>
{nested && renderClosing(propertyValue)}
</div>
);
} else {
return (
<>
<code>{`${propertyValue}`}</code>,
</>
);
}
}
function renderOpening(value: any): string {
return Array.isArray(value) && value.length
? '['
: value && typeof value === 'object'
? '{'
: '';
}
function renderClosing(value: any): string {
return Array.isArray(value) && value.length
? '],'
: value && typeof value === 'object'
? '},'
: '';
}
export default PropertyRenderer;

View File

@ -0,0 +1,10 @@
import { render } from '@testing-library/react';
import Target from './target';
describe('Target', () => {
it('should render successfully', () => {
const { baseElement } = render(<Target />);
expect(baseElement).toBeTruthy();
});
});

View File

@ -0,0 +1,32 @@
/* eslint-disable @nx/enforce-module-boundaries */
// nx-ignore-next-line
import { TargetConfiguration } from '@nx/devkit';
import PropertyRenderer from './property-renderer';
import { useState } from 'react';
/* eslint-disable-next-line */
export interface TargetProps {
targetName: string;
targetConfiguration: TargetConfiguration;
sourceMap: Record<string, string[]>;
}
export function Target(props: TargetProps) {
return (
<div className="ml-3 mb-3">
<h3 className="text-lg font-bold">{props.targetName}</h3>
<div className="ml-3">
{Object.entries(props.targetConfiguration).map(([key, value]) =>
PropertyRenderer({
propertyKey: key,
propertyValue: value,
keyPrefix: `targets.${props.targetName}`,
sourceMap: props.sourceMap,
})
)}
</div>
</div>
);
}
export default Target;

View File

@ -0,0 +1,17 @@
{
"compilerOptions": {
"jsx": "react-jsx",
"allowJs": false,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.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

@ -1,4 +1,4 @@
import { buildProjectGraphWithoutDaemon } from '../src/project-graph/project-graph'; import { buildProjectGraphAndSourceMapsWithoutDaemon } from '../src/project-graph/project-graph';
import { workspaceRoot } from '../src/utils/workspace-root'; import { workspaceRoot } from '../src/utils/workspace-root';
import { fileExists } from '../src/utils/fileutils'; import { fileExists } from '../src/utils/fileutils';
import { join } from 'path'; import { join } from 'path';
@ -20,7 +20,9 @@ import { setupWorkspaceContext } from '../src/utils/workspace-context';
try { try {
await daemonClient.stop(); await daemonClient.stop();
} catch (e) {} } catch (e) {}
const tasks: Array<Promise<any>> = [buildProjectGraphWithoutDaemon()]; const tasks: Array<Promise<any>> = [
buildProjectGraphAndSourceMapsWithoutDaemon(),
];
if (isNxCloudUsed(readNxJson())) { if (isNxCloudUsed(readNxJson())) {
tasks.push(verifyOrUpdateNxCloudClient(getCloudOptions())); tasks.push(verifyOrUpdateNxCloudClient(getCloudOptions()));
} }

View File

@ -33,7 +33,10 @@ import { TaskGraph } from '../../config/task-graph';
import { daemonClient } from '../../daemon/client/client'; import { daemonClient } from '../../daemon/client/client';
import { getRootTsConfigPath } from '../../plugins/js/utils/typescript'; import { getRootTsConfigPath } from '../../plugins/js/utils/typescript';
import { pruneExternalNodes } from '../../project-graph/operators'; import { pruneExternalNodes } from '../../project-graph/operators';
import { createProjectGraphAsync } from '../../project-graph/project-graph'; import {
createProjectGraphAndSourceMapsAsync,
createProjectGraphAsync,
} from '../../project-graph/project-graph';
import { import {
createTaskGraph, createTaskGraph,
mapTargetDefaultsToDependencies, mapTargetDefaultsToDependencies,
@ -45,6 +48,7 @@ import { HashPlanner, transferProjectGraph } from '../../native';
import { transformProjectGraphForRust } from '../../native/transform-objects'; import { transformProjectGraphForRust } from '../../native/transform-objects';
import { getAffectedGraphNodes } from '../affected/affected'; import { getAffectedGraphNodes } from '../affected/affected';
import { readFileMapCache } from '../../project-graph/nx-deps-cache'; import { readFileMapCache } from '../../project-graph/nx-deps-cache';
import { ConfigurationSourceMaps } from '../../project-graph/utils/project-configuration-utils';
import { filterUsingGlobPatterns } from '../../hasher/task-hasher'; import { filterUsingGlobPatterns } from '../../hasher/task-hasher';
@ -94,7 +98,8 @@ function buildEnvironmentJs(
localMode: 'build' | 'serve', localMode: 'build' | 'serve',
depGraphClientResponse?: ProjectGraphClientResponse, depGraphClientResponse?: ProjectGraphClientResponse,
taskGraphClientResponse?: TaskGraphClientResponse, taskGraphClientResponse?: TaskGraphClientResponse,
expandedTaskInputsReponse?: ExpandedTaskInputsReponse expandedTaskInputsReponse?: ExpandedTaskInputsReponse,
sourceMapsResponse?: ConfigurationSourceMaps
) { ) {
let environmentJs = `window.exclude = ${JSON.stringify(exclude)}; let environmentJs = `window.exclude = ${JSON.stringify(exclude)};
window.watch = ${!!watchMode}; window.watch = ${!!watchMode};
@ -111,6 +116,7 @@ function buildEnvironmentJs(
projectGraphUrl: 'project-graph.json', projectGraphUrl: 'project-graph.json',
taskGraphUrl: 'task-graph.json', taskGraphUrl: 'task-graph.json',
taskInputsUrl: 'task-inputs.json', taskInputsUrl: 'task-inputs.json',
sourceMapsUrl: 'source-maps.json'
} }
], ],
defaultWorkspaceId: 'local', defaultWorkspaceId: 'local',
@ -130,10 +136,15 @@ function buildEnvironmentJs(
environmentJs += `window.expandedTaskInputsResponse = ${JSON.stringify( environmentJs += `window.expandedTaskInputsResponse = ${JSON.stringify(
expandedTaskInputsReponse expandedTaskInputsReponse
)};`; )};`;
environmentJs += `window.sourceMapsResponse = ${JSON.stringify(
sourceMapsResponse
)};`;
} else { } else {
environmentJs += `window.projectGraphResponse = null;`; environmentJs += `window.projectGraphResponse = null;`;
environmentJs += `window.taskGraphResponse = null;`; environmentJs += `window.taskGraphResponse = null;`;
environmentJs += `window.expandedTaskInputsResponse = null;`; environmentJs += `window.expandedTaskInputsResponse = null;`;
environmentJs += `window.sourceMapsResponse = null;`;
} }
return environmentJs; return environmentJs;
@ -214,7 +225,7 @@ export async function generateGraph(
groupByFolder?: boolean; groupByFolder?: boolean;
watch?: boolean; watch?: boolean;
open?: boolean; open?: boolean;
view: 'projects' | 'tasks'; view: 'projects' | 'tasks' | 'project-details';
projects?: string[]; projects?: string[];
all?: boolean; all?: boolean;
targets?: string[]; targets?: string[];
@ -238,12 +249,33 @@ export async function generateGraph(
}); });
} }
if (args.view === 'project-details' && !args.focus) {
output.error({
title: `The project details view requires the --focus option.`,
});
process.exit(1);
}
if (args.view === 'project-details' && (args.targets || args.affected)) {
output.error({
title: `The project details view can only be used with the --focus option.`,
bodyLines: [
`You passed ${args.targets ? '--targets ' : ''}${
args.affected ? '--affected ' : ''
}`,
],
});
process.exit(1);
}
// TODO: Graph Client should support multiple targets // TODO: Graph Client should support multiple targets
const target = Array.isArray(args.targets && args.targets.length >= 1) const target = Array.isArray(args.targets && args.targets.length >= 1)
? args.targets[0] ? args.targets[0]
: args.targets; : args.targets;
const rawGraph = await createProjectGraphAsync({ exitOnError: true }); const { projectGraph: rawGraph, sourceMaps } =
await createProjectGraphAndSourceMapsAsync({
exitOnError: true,
});
let prunedGraph = pruneExternalNodes(rawGraph); let prunedGraph = pruneExternalNodes(rawGraph);
const projects = Object.values( const projects = Object.values(
@ -339,23 +371,23 @@ export async function generateGraph(
}, },
}); });
const depGraphClientResponse = await createDepGraphClientResponse( const { projectGraphClientResponse } =
affectedProjects await createProjectGraphAndSourceMapClientResponse(affectedProjects);
);
const taskGraphClientResponse = await createTaskGraphClientResponse(); const taskGraphClientResponse = await createTaskGraphClientResponse();
const taskInputsReponse = await createExpandedTaskInputResponse( const taskInputsReponse = await createExpandedTaskInputResponse(
taskGraphClientResponse, taskGraphClientResponse,
depGraphClientResponse projectGraphClientResponse
); );
const environmentJs = buildEnvironmentJs( const environmentJs = buildEnvironmentJs(
args.exclude || [], args.exclude || [],
args.watch, args.watch,
!!args.file && args.file.endsWith('html') ? 'build' : 'serve', !!args.file && args.file.endsWith('html') ? 'build' : 'serve',
depGraphClientResponse, projectGraphClientResponse,
taskGraphClientResponse, taskGraphClientResponse,
taskInputsReponse taskInputsReponse,
sourceMaps
); );
html = html.replace(/src="/g, 'src="static/'); html = html.replace(/src="/g, 'src="static/');
html = html.replace(/href="styles/g, 'href="static/styles'); html = html.replace(/href="styles/g, 'href="static/styles');
@ -472,10 +504,13 @@ async function startServer(
unregisterFileWatcher = await createFileWatcher(); unregisterFileWatcher = await createFileWatcher();
} }
currentDepGraphClientResponse = await createDepGraphClientResponse(affected); const { projectGraphClientResponse, sourceMapResponse } =
currentDepGraphClientResponse.focus = focus; await createProjectGraphAndSourceMapClientResponse(affected);
currentDepGraphClientResponse.groupByFolder = groupByFolder;
currentDepGraphClientResponse.exclude = exclude; currentProjectGraphClientResponse = projectGraphClientResponse;
currentProjectGraphClientResponse.focus = focus;
currentProjectGraphClientResponse.groupByFolder = groupByFolder;
currentProjectGraphClientResponse.exclude = exclude;
const app = http.createServer(async (req, res) => { const app = http.createServer(async (req, res) => {
// parse URL // parse URL
@ -488,7 +523,7 @@ async function startServer(
const sanitizePath = basename(parsedUrl.pathname); const sanitizePath = basename(parsedUrl.pathname);
if (sanitizePath === 'project-graph.json') { if (sanitizePath === 'project-graph.json') {
res.writeHead(200, { 'Content-Type': 'application/json' }); res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(currentDepGraphClientResponse)); res.end(JSON.stringify(currentProjectGraphClientResponse));
return; return;
} }
@ -515,9 +550,15 @@ async function startServer(
return; return;
} }
if (sanitizePath === 'source-maps.json') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(currentSourceMapsClientResponse));
return;
}
if (sanitizePath === 'currentHash') { if (sanitizePath === 'currentHash') {
res.writeHead(200, { 'Content-Type': 'application/json' }); res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ hash: currentDepGraphClientResponse.hash })); res.end(JSON.stringify({ hash: currentProjectGraphClientResponse.hash }));
return; return;
} }
@ -563,7 +604,7 @@ async function startServer(
}); });
} }
let currentDepGraphClientResponse: ProjectGraphClientResponse = { let currentProjectGraphClientResponse: ProjectGraphClientResponse = {
hash: null, hash: null,
projects: [], projects: [],
dependencies: {}, dependencies: {},
@ -577,6 +618,7 @@ let currentDepGraphClientResponse: ProjectGraphClientResponse = {
groupByFolder: false, groupByFolder: false,
exclude: [], exclude: [],
}; };
let currentSourceMapsClientResponse: ConfigurationSourceMaps = {};
function debounce(fn: (...args) => void, time: number) { function debounce(fn: (...args) => void, time: number) {
let timeout: NodeJS.Timeout; let timeout: NodeJS.Timeout;
@ -602,14 +644,17 @@ function createFileWatcher() {
} else if (changes !== null && changes.changedFiles.length > 0) { } else if (changes !== null && changes.changedFiles.length > 0) {
output.note({ title: 'Recalculating project graph...' }); output.note({ title: 'Recalculating project graph...' });
const newGraphClientResponse = await createDepGraphClientResponse(); const { projectGraphClientResponse, sourceMapResponse } =
await createProjectGraphAndSourceMapClientResponse();
if ( if (
newGraphClientResponse.hash !== currentDepGraphClientResponse.hash projectGraphClientResponse.hash !==
currentProjectGraphClientResponse.hash
) { ) {
output.note({ title: 'Graph changes updated.' }); output.note({ title: 'Graph changes updated.' });
currentDepGraphClientResponse = newGraphClientResponse; currentProjectGraphClientResponse = projectGraphClientResponse;
currentSourceMapsClientResponse = sourceMapResponse;
} else { } else {
output.note({ title: 'No graph changes found.' }); output.note({ title: 'No graph changes found.' });
} }
@ -618,14 +663,18 @@ function createFileWatcher() {
); );
} }
async function createDepGraphClientResponse( async function createProjectGraphAndSourceMapClientResponse(
affected: string[] = [] affected: string[] = []
): Promise<ProjectGraphClientResponse> { ): Promise<{
projectGraphClientResponse: ProjectGraphClientResponse;
sourceMapResponse: ConfigurationSourceMaps;
}> {
performance.mark('project graph watch calculation:start'); performance.mark('project graph watch calculation:start');
let graph = pruneExternalNodes( const { projectGraph, sourceMaps } =
await createProjectGraphAsync({ exitOnError: true }) await createProjectGraphAndSourceMapsAsync({ exitOnError: true });
);
let graph = pruneExternalNodes(projectGraph);
let fileMap = readFileMapCache().fileMap.projectFileMap; let fileMap = readFileMapCache().fileMap.projectFileMap;
performance.mark('project graph watch calculation:end'); performance.mark('project graph watch calculation:end');
performance.mark('project graph response generation:start'); performance.mark('project graph response generation:start');
@ -654,13 +703,16 @@ async function createDepGraphClientResponse(
); );
return { return {
...currentDepGraphClientResponse, projectGraphClientResponse: {
hash, ...currentProjectGraphClientResponse,
layout, hash,
projects, layout,
dependencies, projects,
affected, dependencies,
fileMap, affected,
fileMap,
},
sourceMapResponse: sourceMaps,
}; };
} }
@ -846,9 +898,11 @@ async function getExpandedTaskInputs(
if (inputs) { if (inputs) {
return expandInputs( return expandInputs(
inputs, inputs,
currentDepGraphClientResponse.projects.find((p) => p.name === project), currentProjectGraphClientResponse.projects.find(
(p) => p.name === project
),
allWorkspaceFiles, allWorkspaceFiles,
currentDepGraphClientResponse currentProjectGraphClientResponse
); );
} }
return {}; return {};

View File

@ -21,6 +21,7 @@ export type ShowProjectsOptions = NxShowArgs & {
export type ShowProjectOptions = NxShowArgs & { export type ShowProjectOptions = NxShowArgs & {
projectName: string; projectName: string;
web?: boolean;
}; };
export const yargsShowCommand: CommandModule< export const yargsShowCommand: CommandModule<
@ -121,6 +122,17 @@ const showProjectCommand: CommandModule<NxShowArgs, ShowProjectOptions> = {
description: 'Which project should be viewed?', description: 'Which project should be viewed?',
}) })
.default('json', true) .default('json', true)
.option('web', {
type: 'boolean',
description: 'Show project details in the browser',
hidden: true,
})
.check((argv) => {
if (argv.web) {
argv.json = false;
}
return true;
})
.example( .example(
'$0 show project my-app', '$0 show project my-app',
'View project information for my-app in JSON format' 'View project information for my-app in JSON format'

View File

@ -19,6 +19,7 @@ import {
} from '../../utils/command-line-utils'; } from '../../utils/command-line-utils';
import { findMatchingProjects } from '../../utils/find-matching-projects'; import { findMatchingProjects } from '../../utils/find-matching-projects';
import { ShowProjectOptions, ShowProjectsOptions } from './command-object'; import { ShowProjectOptions, ShowProjectsOptions } from './command-object';
import { generateGraph } from '../graph/graph';
export async function showProjectsHandler( export async function showProjectsHandler(
args: ShowProjectsOptions args: ShowProjectsOptions
@ -94,6 +95,16 @@ export async function showProjectHandler(
} }
if (args.json) { if (args.json) {
console.log(JSON.stringify(node.data)); console.log(JSON.stringify(node.data));
} else if (args.web) {
await generateGraph(
{
view: 'project-details',
focus: node.name,
watch: false,
open: true,
},
[]
);
} else { } else {
const chalk = require('chalk') as typeof import('chalk'); const chalk = require('chalk') as typeof import('chalk');
const logIfExists = (label, key: keyof typeof node['data']) => { const logIfExists = (label, key: keyof typeof node['data']) => {

View File

@ -24,6 +24,7 @@ import { Message, SocketMessenger } from './socket-messenger';
import { safelyCleanUpExistingProcess } from '../cache'; import { safelyCleanUpExistingProcess } from '../cache';
import { Hash } from '../../hasher/task-hasher'; import { Hash } from '../../hasher/task-hasher';
import { Task, TaskGraph } from '../../config/task-graph'; import { Task, TaskGraph } from '../../config/task-graph';
import { ConfigurationSourceMaps } from '../../project-graph/utils/project-configuration-utils';
const DAEMON_ENV_SETTINGS = { const DAEMON_ENV_SETTINGS = {
...process.env, ...process.env,
@ -124,9 +125,17 @@ export class DaemonClient {
return this.sendToDaemonViaQueue({ type: 'REQUEST_SHUTDOWN' }); return this.sendToDaemonViaQueue({ type: 'REQUEST_SHUTDOWN' });
} }
async getProjectGraph(): Promise<ProjectGraph> { async getProjectGraphAndSourceMaps(): Promise<{
return (await this.sendToDaemonViaQueue({ type: 'REQUEST_PROJECT_GRAPH' })) projectGraph: ProjectGraph;
.projectGraph; sourceMaps: ConfigurationSourceMaps;
}> {
const response = await this.sendToDaemonViaQueue({
type: 'REQUEST_PROJECT_GRAPH',
});
return {
projectGraph: response.projectGraph,
sourceMaps: response.sourceMaps,
};
} }
async getAllFileData(): Promise<FileData[]> { async getAllFileData(): Promise<FileData[]> {
@ -162,7 +171,7 @@ export class DaemonClient {
} | null } | null
) => void ) => void
): Promise<UnregisterCallback> { ): Promise<UnregisterCallback> {
await this.getProjectGraph(); await this.getProjectGraphAndSourceMaps();
let messenger: SocketMessenger | undefined; let messenger: SocketMessenger | undefined;
await this.queue.sendToQueue(() => { await this.queue.sendToQueue(() => {

View File

@ -19,7 +19,8 @@ export async function handleRequestProjectGraph(): Promise<HandlerResult> {
const serializedResult = serializeResult( const serializedResult = serializeResult(
result.error, result.error,
result.serializedProjectGraph result.serializedProjectGraph,
result.serializedSourceMaps
); );
if (!serializedResult) { if (!serializedResult) {
return { return {

View File

@ -1,5 +1,5 @@
import { performance } from 'perf_hooks'; import { performance } from 'perf_hooks';
import { readNxJson, NxJsonConfiguration } from '../../config/nx-json'; import { readNxJson } from '../../config/nx-json';
import { import {
FileData, FileData,
FileMap, FileMap,
@ -21,7 +21,6 @@ import {
retrieveWorkspaceFiles, retrieveWorkspaceFiles,
} from '../../project-graph/utils/retrieve-workspace-files'; } from '../../project-graph/utils/retrieve-workspace-files';
import { fileExists } from '../../utils/fileutils'; import { fileExists } from '../../utils/fileutils';
import { writeSourceMaps } from '../../utils/source-maps';
import { import {
resetWorkspaceContext, resetWorkspaceContext,
updateFilesInContext, updateFilesInContext,
@ -31,14 +30,17 @@ import { notifyFileWatcherSockets } from './file-watching/file-watcher-sockets';
import { serverLogger } from './logger'; import { serverLogger } from './logger';
import { NxWorkspaceFilesExternals } from '../../native'; import { NxWorkspaceFilesExternals } from '../../native';
let cachedSerializedProjectGraphPromise: Promise<{ interface SerializedProjectGraph {
error: Error | null; error: Error | null;
projectGraph: ProjectGraph | null; projectGraph: ProjectGraph | null;
fileMap: FileMap | null; fileMap: FileMap | null;
allWorkspaceFiles: FileData[] | null; allWorkspaceFiles: FileData[] | null;
serializedProjectGraph: string | null; serializedProjectGraph: string | null;
serializedSourceMaps: string | null;
rustReferences: NxWorkspaceFilesExternals | null; rustReferences: NxWorkspaceFilesExternals | null;
}>; }
let cachedSerializedProjectGraphPromise: Promise<SerializedProjectGraph>;
export let fileMapWithFiles: export let fileMapWithFiles:
| { | {
fileMap: FileMap; fileMap: FileMap;
@ -56,7 +58,7 @@ let waitPeriod = 100;
let scheduledTimeoutId; let scheduledTimeoutId;
let knownExternalNodes: Record<string, ProjectGraphExternalNode> = {}; let knownExternalNodes: Record<string, ProjectGraphExternalNode> = {};
export async function getCachedSerializedProjectGraphPromise() { export async function getCachedSerializedProjectGraphPromise(): Promise<SerializedProjectGraph> {
try { try {
// recomputing it now on demand. we can ignore the scheduled timeout // recomputing it now on demand. we can ignore the scheduled timeout
if (scheduledTimeoutId) { if (scheduledTimeoutId) {
@ -81,6 +83,7 @@ export async function getCachedSerializedProjectGraphPromise() {
return { return {
error: e, error: e,
serializedProjectGraph: null, serializedProjectGraph: null,
serializedSourceMaps: null,
projectGraph: null, projectGraph: null,
fileMap: null, fileMap: null,
allWorkspaceFiles: null, allWorkspaceFiles: null,
@ -196,7 +199,7 @@ async function processCollectedUpdatedAndDeletedFiles(
} }
} }
async function processFilesAndCreateAndSerializeProjectGraph() { async function processFilesAndCreateAndSerializeProjectGraph(): Promise<SerializedProjectGraph> {
try { try {
performance.mark('hash-watched-changes-start'); performance.mark('hash-watched-changes-start');
const updatedFiles = [...collectedUpdatedFiles.values()]; const updatedFiles = [...collectedUpdatedFiles.values()];
@ -214,17 +217,16 @@ async function processFilesAndCreateAndSerializeProjectGraph() {
serverLogger.requestLog([...updatedFiles.values()]); serverLogger.requestLog([...updatedFiles.values()]);
serverLogger.requestLog([...deletedFiles]); serverLogger.requestLog([...deletedFiles]);
const nxJson = readNxJson(workspaceRoot); const nxJson = readNxJson(workspaceRoot);
const configResult = await retrieveProjectConfigurations( const graphNodes = await retrieveProjectConfigurations(
workspaceRoot, workspaceRoot,
nxJson nxJson
); );
await processCollectedUpdatedAndDeletedFiles( await processCollectedUpdatedAndDeletedFiles(
configResult, graphNodes,
updatedFileHashes, updatedFileHashes,
deletedFiles deletedFiles
); );
writeSourceMaps(configResult.sourceMaps); return createAndSerializeProjectGraph(graphNodes);
return createAndSerializeProjectGraph(configResult);
} catch (err) { } catch (err) {
return Promise.resolve({ return Promise.resolve({
error: err, error: err,
@ -233,6 +235,7 @@ async function processFilesAndCreateAndSerializeProjectGraph() {
rustReferences: null, rustReferences: null,
allWorkspaceFiles: null, allWorkspaceFiles: null,
serializedProjectGraph: null, serializedProjectGraph: null,
serializedSourceMaps: null,
}); });
} }
} }
@ -254,14 +257,8 @@ function copyFileMap(m: FileMap) {
async function createAndSerializeProjectGraph({ async function createAndSerializeProjectGraph({
projects, projects,
}: RetrievedGraphNodes): Promise<{ sourceMaps,
error: string | null; }: RetrievedGraphNodes): Promise<SerializedProjectGraph> {
projectGraph: ProjectGraph | null;
fileMap: FileMap | null;
allWorkspaceFiles: FileData[] | null;
rustReferences: NxWorkspaceFilesExternals | null;
serializedProjectGraph: string | null;
}> {
try { try {
performance.mark('create-project-graph-start'); performance.mark('create-project-graph-start');
const fileMap = copyFileMap(fileMapWithFiles.fileMap); const fileMap = copyFileMap(fileMapWithFiles.fileMap);
@ -289,6 +286,7 @@ async function createAndSerializeProjectGraph({
performance.mark('json-stringify-start'); performance.mark('json-stringify-start');
const serializedProjectGraph = JSON.stringify(projectGraph); const serializedProjectGraph = JSON.stringify(projectGraph);
const serializedSourceMaps = JSON.stringify(sourceMaps);
performance.mark('json-stringify-end'); performance.mark('json-stringify-end');
performance.measure( performance.measure(
'serialize graph', 'serialize graph',
@ -302,6 +300,7 @@ async function createAndSerializeProjectGraph({
fileMap, fileMap,
allWorkspaceFiles, allWorkspaceFiles,
serializedProjectGraph, serializedProjectGraph,
serializedSourceMaps,
rustReferences, rustReferences,
}; };
} catch (e) { } catch (e) {
@ -314,6 +313,7 @@ async function createAndSerializeProjectGraph({
fileMap: null, fileMap: null,
allWorkspaceFiles: null, allWorkspaceFiles: null,
serializedProjectGraph: null, serializedProjectGraph: null,
serializedSourceMaps: null,
rustReferences: null, rustReferences: null,
}; };
} }

View File

@ -99,6 +99,6 @@ export async function respondWithErrorAndExit(
error.message = `${error.message}\n\nBecause of the error the Nx daemon process has exited. The next Nx command is going to restart the daemon process.\nIf the error persists, please run "nx reset".`; error.message = `${error.message}\n\nBecause of the error the Nx daemon process has exited. The next Nx command is going to restart the daemon process.\nIf the error persists, please run "nx reset".`;
await respondToClient(socket, serializeResult(error, null), null); await respondToClient(socket, serializeResult(error, null, null), null);
process.exit(1); process.exit(1);
} }

View File

@ -32,10 +32,11 @@ function serializeError(error: Error | null): string | null {
// Prepare a serialized project graph result for sending over IPC from the server to the client // Prepare a serialized project graph result for sending over IPC from the server to the client
export function serializeResult( export function serializeResult(
error: Error | null, error: Error | null,
serializedProjectGraph: string | null serializedProjectGraph: string | null,
serializedSourceMaps: string | null
): string | null { ): string | null {
// We do not want to repeat work `JSON.stringify`ing an object containing the potentially large project graph so merge as strings // We do not want to repeat work `JSON.stringify`ing an object containing the potentially large project graph so merge as strings
return `{ "error": ${serializeError( return `{ "error": ${serializeError(
error error
)}, "projectGraph": ${serializedProjectGraph} }`; )}, "projectGraph": ${serializedProjectGraph}, "sourceMaps": ${serializedSourceMaps} }`;
} }

View File

@ -18,7 +18,6 @@ import {
} from './utils/retrieve-workspace-files'; } from './utils/retrieve-workspace-files';
import { readNxJson } from '../config/nx-json'; import { readNxJson } from '../config/nx-json';
import { unregisterPluginTSTranspiler } from '../utils/nx-plugin'; import { unregisterPluginTSTranspiler } from '../utils/nx-plugin';
import { writeSourceMaps } from '../utils/source-maps';
/** /**
* Synchronously reads the latest cached copy of the workspace's ProjectGraph. * Synchronously reads the latest cached copy of the workspace's ProjectGraph.
@ -78,7 +77,7 @@ export function readProjectsConfigurationFromProjectGraph(
}; };
} }
export async function buildProjectGraphWithoutDaemon() { export async function buildProjectGraphAndSourceMapsWithoutDaemon() {
const nxJson = readNxJson(); const nxJson = readNxJson();
performance.mark('retrieve-project-configurations:start'); performance.mark('retrieve-project-configurations:start');
@ -106,11 +105,9 @@ export async function buildProjectGraphWithoutDaemon() {
).projectGraph; ).projectGraph;
performance.mark('build-project-graph-using-project-file-map:end'); performance.mark('build-project-graph-using-project-file-map:end');
writeSourceMaps(sourceMaps);
unregisterPluginTSTranspiler(); unregisterPluginTSTranspiler();
return projectGraph; return { projectGraph, sourceMaps };
} }
function handleProjectGraphError(opts: { exitOnError: boolean }, e) { function handleProjectGraphError(opts: { exitOnError: boolean }, e) {
@ -156,11 +153,23 @@ export async function createProjectGraphAsync(
resetDaemonClient: false, resetDaemonClient: false,
} }
): Promise<ProjectGraph> { ): Promise<ProjectGraph> {
const projectGraphAndSourceMaps = await createProjectGraphAndSourceMapsAsync(
opts
);
return projectGraphAndSourceMaps.projectGraph;
}
export async function createProjectGraphAndSourceMapsAsync(
opts: { exitOnError: boolean; resetDaemonClient?: boolean } = {
exitOnError: false,
resetDaemonClient: false,
}
) {
performance.mark('create-project-graph-async:start'); performance.mark('create-project-graph-async:start');
if (!daemonClient.enabled()) { if (!daemonClient.enabled()) {
try { try {
const res = await buildProjectGraphWithoutDaemon(); const res = await buildProjectGraphAndSourceMapsWithoutDaemon();
performance.measure( performance.measure(
'create-project-graph-async >> retrieve-project-configurations', 'create-project-graph-async >> retrieve-project-configurations',
'retrieve-project-configurations:start', 'retrieve-project-configurations:start',
@ -188,7 +197,8 @@ export async function createProjectGraphAsync(
} }
} else { } else {
try { try {
const projectGraph = await daemonClient.getProjectGraph(); const projectGraphAndSourceMaps =
await daemonClient.getProjectGraphAndSourceMaps();
if (opts.resetDaemonClient) { if (opts.resetDaemonClient) {
daemonClient.reset(); daemonClient.reset();
} }
@ -198,7 +208,7 @@ export async function createProjectGraphAsync(
'create-project-graph-async:start', 'create-project-graph-async:start',
'create-project-graph-async:end' 'create-project-graph-async:end'
); );
return projectGraph; return projectGraphAndSourceMaps;
} catch (e) { } catch (e) {
if (e.message.indexOf('inotify_add_watch') > -1) { if (e.message.indexOf('inotify_add_watch') > -1) {
// common errors with the daemon due to OS settings (cannot watch all the files available) // common errors with the daemon due to OS settings (cannot watch all the files available)
@ -210,7 +220,7 @@ export async function createProjectGraphAsync(
], ],
}); });
markDaemonAsDisabled(); markDaemonAsDisabled();
return buildProjectGraphWithoutDaemon(); return buildProjectGraphAndSourceMapsWithoutDaemon();
} }
if (e.internalDaemonError) { if (e.internalDaemonError) {
@ -224,7 +234,7 @@ export async function createProjectGraphAsync(
], ],
}); });
markDaemonAsDisabled(); markDaemonAsDisabled();
return buildProjectGraphWithoutDaemon(); return buildProjectGraphAndSourceMapsWithoutDaemon();
} }
handleProjectGraphError(opts, e); handleProjectGraphError(opts, e);

View File

@ -1,9 +1,6 @@
import { performance } from 'perf_hooks'; import { performance } from 'perf_hooks';
import { getNxRequirePaths } from '../../utils/installation-directory'; import { getNxRequirePaths } from '../../utils/installation-directory';
import { import { ProjectConfiguration } from '../../config/workspace-json-project-json';
ProjectConfiguration,
ProjectsConfigurations,
} from '../../config/workspace-json-project-json';
import { import {
NX_ANGULAR_JSON_PLUGIN_NAME, NX_ANGULAR_JSON_PLUGIN_NAME,
NxAngularJsonPlugin, NxAngularJsonPlugin,
@ -11,11 +8,10 @@ import {
} from '../../adapter/angular-json'; } from '../../adapter/angular-json';
import { NxJsonConfiguration, readNxJson } from '../../config/nx-json'; import { NxJsonConfiguration, readNxJson } from '../../config/nx-json';
import { ProjectGraphExternalNode } from '../../config/project-graph'; import { ProjectGraphExternalNode } from '../../config/project-graph';
import type { NxWorkspaceFiles } from '../../native';
import { getNxPackageJsonWorkspacesPlugin } from '../../../plugins/package-json-workspaces'; import { getNxPackageJsonWorkspacesPlugin } from '../../../plugins/package-json-workspaces';
import { import {
ConfigurationSourceMaps,
buildProjectsConfigurationsFromProjectPathsAndPlugins, buildProjectsConfigurationsFromProjectPathsAndPlugins,
ConfigurationSourceMaps,
} from './project-configuration-utils'; } from './project-configuration-utils';
import { import {
getDefaultPlugins, getDefaultPlugins,
@ -24,8 +20,8 @@ import {
} from '../../utils/nx-plugin'; } from '../../utils/nx-plugin';
import { CreateProjectJsonProjectsPlugin } from '../../plugins/project-json/build-nodes/project-json'; import { CreateProjectJsonProjectsPlugin } from '../../plugins/project-json/build-nodes/project-json';
import { import {
globWithWorkspaceContext,
getNxWorkspaceFilesFromContext, getNxWorkspaceFilesFromContext,
globWithWorkspaceContext,
} from '../../utils/workspace-context'; } from '../../utils/workspace-context';
import { buildAllWorkspaceFiles } from './build-all-workspace-files'; import { buildAllWorkspaceFiles } from './build-all-workspace-files';

View File

@ -1,12 +0,0 @@
import { join } from 'path';
import { projectGraphCacheDirectory } from './cache-directory';
import { writeJsonFile } from './fileutils';
export function writeSourceMaps(
sourceMaps: Record<string, Record<string, string[]>>
) {
writeJsonFile(
join(projectGraphCacheDirectory, 'configuration-source-maps.json'),
sourceMaps
);
}

View File

@ -34,6 +34,7 @@ function writeFile() {
projectGraphUrl: join('assets/generated-project-graphs/', filename), projectGraphUrl: join('assets/generated-project-graphs/', filename),
taskGraphUrl: join('assets/generated-task-graphs/', filename), taskGraphUrl: join('assets/generated-task-graphs/', filename),
taskInputsUrl: join('assets/generated-task-inputs/', filename), taskInputsUrl: join('assets/generated-task-inputs/', filename),
sourceMapsUrl: join('assets/generated-source-maps/', filename),
}; };
}); });
} catch { } catch {
@ -52,6 +53,7 @@ function writeFile() {
projectGraphUrl: join('assets/project-graphs/', filename), projectGraphUrl: join('assets/project-graphs/', filename),
taskGraphUrl: join('assets/task-graphs/', filename), taskGraphUrl: join('assets/task-graphs/', filename),
taskInputsUrl: join('assets/task-inputs/', filename), taskInputsUrl: join('assets/task-inputs/', filename),
sourceMapsUrl: join('assets/source-maps/', filename),
}; };
}); });
} catch { } catch {

View File

@ -36,6 +36,10 @@ async function generateGraph(directory: string, name: string) {
/window.expandedTaskInputsResponse = (.*?);/ /window.expandedTaskInputsResponse = (.*?);/
); );
const sourceMapsResponse = environmentJs.match(
/window.sourceMapsResponse = (.*?);/
);
ensureDirSync( ensureDirSync(
join(__dirname, '../graph/client/src/assets/generated-project-graphs/') join(__dirname, '../graph/client/src/assets/generated-project-graphs/')
); );
@ -45,6 +49,9 @@ async function generateGraph(directory: string, name: string) {
ensureDirSync( ensureDirSync(
join(__dirname, '../graph/client/src/assets/generated-task-inputs/') join(__dirname, '../graph/client/src/assets/generated-task-inputs/')
); );
ensureDirSync(
join(__dirname, '../graph/client/src/assets/generated-source-maps/')
);
writeFileSync( writeFileSync(
join( join(
@ -72,6 +79,15 @@ async function generateGraph(directory: string, name: string) {
), ),
expandedTaskInputsReponse[1] expandedTaskInputsReponse[1]
); );
writeFileSync(
join(
__dirname,
'../graph/client/src/assets/generated-source-maps/',
`${name}.json`
),
sourceMapsResponse[1]
);
} }
(async () => { (async () => {

View File

@ -35,6 +35,7 @@
"@nx/expo": ["packages/expo"], "@nx/expo": ["packages/expo"],
"@nx/expo/*": ["packages/expo/*"], "@nx/expo/*": ["packages/expo/*"],
"@nx/express": ["packages/express"], "@nx/express": ["packages/express"],
"@nx/graph/project-details": ["graph/project-details/src/index.ts"],
"@nx/graph/ui-components": ["graph/ui-components/src/index.ts"], "@nx/graph/ui-components": ["graph/ui-components/src/index.ts"],
"@nx/graph/ui-graph": ["graph/ui-graph/src/index.ts"], "@nx/graph/ui-graph": ["graph/ui-graph/src/index.ts"],
"@nx/graph/ui-tooltips": ["graph/ui-tooltips/src/index.ts"], "@nx/graph/ui-tooltips": ["graph/ui-tooltips/src/index.ts"],