feat(graph): add nx console data loader (#20744)
This commit is contained in:
parent
b60ae51100
commit
b97c869279
133
graph/client/src/app/external-api-impl.ts
Normal file
133
graph/client/src/app/external-api-impl.ts
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
import { ExternalApi, getExternalApiService } from '@nx/graph/shared';
|
||||||
|
import { getRouter } from './get-router';
|
||||||
|
import { getProjectGraphService } from './machines/get-services';
|
||||||
|
import { getGraphService } from './machines/graph.service';
|
||||||
|
|
||||||
|
export class ExternalApiImpl extends ExternalApi {
|
||||||
|
_projectGraphService = getProjectGraphService();
|
||||||
|
_graphIsReady = new Promise<void>((resolve) => {
|
||||||
|
this._projectGraphService.subscribe((state) => {
|
||||||
|
if (!state.matches('idle')) {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
_graphService = getGraphService();
|
||||||
|
|
||||||
|
router = getRouter();
|
||||||
|
externalApiService = getExternalApiService();
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.externalApiService.subscribe(
|
||||||
|
({ type, payload }: { type: string; payload: any }) => {
|
||||||
|
if (!this.graphInteractionEventListener) {
|
||||||
|
console.log('graphInteractionEventListener not registered.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (type === 'file-click') {
|
||||||
|
const url = `${payload.sourceRoot}/${payload.file}`;
|
||||||
|
this.graphInteractionEventListener({
|
||||||
|
type: 'file-click',
|
||||||
|
payload: { url },
|
||||||
|
});
|
||||||
|
} else if (type === 'open-project-config') {
|
||||||
|
this.graphInteractionEventListener({
|
||||||
|
type: 'open-project-config',
|
||||||
|
payload,
|
||||||
|
});
|
||||||
|
} else if (type === 'run-task') {
|
||||||
|
this.graphInteractionEventListener({
|
||||||
|
type: 'run-task',
|
||||||
|
payload,
|
||||||
|
});
|
||||||
|
} else if (type === 'open-project-graph') {
|
||||||
|
this.graphInteractionEventListener({
|
||||||
|
type: 'open-project-graph',
|
||||||
|
payload,
|
||||||
|
});
|
||||||
|
} else if (type === 'open-task-graph') {
|
||||||
|
this.graphInteractionEventListener({
|
||||||
|
type: 'open-task-graph',
|
||||||
|
payload,
|
||||||
|
});
|
||||||
|
} else if (type === 'override-target') {
|
||||||
|
this.graphInteractionEventListener({
|
||||||
|
type: 'override-target',
|
||||||
|
payload,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log('unhandled event', type, payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// make sure properties set before are taken into account again
|
||||||
|
if (window.externalApi?.loadProjectGraph) {
|
||||||
|
this.loadProjectGraph = window.externalApi.loadProjectGraph;
|
||||||
|
}
|
||||||
|
if (window.externalApi?.loadTaskGraph) {
|
||||||
|
this.loadTaskGraph = window.externalApi.loadTaskGraph;
|
||||||
|
}
|
||||||
|
if (window.externalApi?.loadExpandedTaskInputs) {
|
||||||
|
this.loadExpandedTaskInputs = window.externalApi.loadExpandedTaskInputs;
|
||||||
|
}
|
||||||
|
if (window.externalApi?.loadSourceMaps) {
|
||||||
|
this.loadSourceMaps = window.externalApi.loadSourceMaps;
|
||||||
|
}
|
||||||
|
if (window.externalApi?.graphInteractionEventListener) {
|
||||||
|
this.graphInteractionEventListener =
|
||||||
|
window.externalApi.graphInteractionEventListener;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
focusProject(projectName: string) {
|
||||||
|
this.router.navigate(`/projects/${encodeURIComponent(projectName)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleSelectProject(projectName: string) {
|
||||||
|
this._graphIsReady.then(() => {
|
||||||
|
const projectSelected = this._projectGraphService
|
||||||
|
.getSnapshot()
|
||||||
|
.context.selectedProjects.find((p) => p === projectName);
|
||||||
|
if (!projectSelected) {
|
||||||
|
this._projectGraphService.send({ type: 'selectProject', projectName });
|
||||||
|
} else {
|
||||||
|
this._projectGraphService.send({
|
||||||
|
type: 'deselectProject',
|
||||||
|
projectName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
selectAllProjects() {
|
||||||
|
this.router.navigate(`/projects/all`);
|
||||||
|
}
|
||||||
|
|
||||||
|
showAffectedProjects() {
|
||||||
|
this.router.navigate(`/projects/affected`);
|
||||||
|
}
|
||||||
|
|
||||||
|
focusTarget(projectName: string, targetName: string) {
|
||||||
|
this.router.navigate(
|
||||||
|
`/tasks/${encodeURIComponent(targetName)}?projects=${encodeURIComponent(
|
||||||
|
projectName
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
selectAllTargetsByName(targetName: string) {
|
||||||
|
this.router.navigate(`/tasks/${encodeURIComponent(targetName)}/all`);
|
||||||
|
}
|
||||||
|
|
||||||
|
enableExperimentalFeatures() {
|
||||||
|
localStorage.setItem('showExperimentalFeatures', 'true');
|
||||||
|
window.appConfig.showExperimentalFeatures = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
disableExperimentalFeatures() {
|
||||||
|
localStorage.setItem('showExperimentalFeatures', 'false');
|
||||||
|
window.appConfig.showExperimentalFeatures = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,93 +0,0 @@
|
|||||||
import { getRouter } from './get-router';
|
|
||||||
import { getProjectGraphService } from './machines/get-services';
|
|
||||||
import { ProjectGraphMachineEvents } from './feature-projects/machines/interfaces';
|
|
||||||
import { getGraphService } from './machines/graph.service';
|
|
||||||
|
|
||||||
export class ExternalApi {
|
|
||||||
_projectGraphService = getProjectGraphService();
|
|
||||||
_graphIsReady = new Promise<void>((resolve) => {
|
|
||||||
this._projectGraphService.subscribe((state) => {
|
|
||||||
if (!state.matches('idle')) {
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
router = getRouter();
|
|
||||||
graphService = getGraphService();
|
|
||||||
|
|
||||||
projectGraphService = {
|
|
||||||
send: (event: ProjectGraphMachineEvents) => {
|
|
||||||
this.handleLegacyProjectGraphEvent(event);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
private fileClickCallbackListeners: ((url: string) => void)[] = [];
|
|
||||||
private openProjectConfigCallbackListeners: ((
|
|
||||||
projectName: string
|
|
||||||
) => void)[] = [];
|
|
||||||
private runTaskCallbackListeners: ((taskId: string) => void)[] = [];
|
|
||||||
|
|
||||||
get depGraphService() {
|
|
||||||
return this.projectGraphService;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.graphService.listen((event) => {
|
|
||||||
if (event.type === 'FileLinkClick') {
|
|
||||||
const url = `${event.sourceRoot}/${event.file}`;
|
|
||||||
this.fileClickCallbackListeners.forEach((cb) => cb(url));
|
|
||||||
}
|
|
||||||
if (event.type === 'ProjectOpenConfigClick') {
|
|
||||||
this.openProjectConfigCallbackListeners.forEach((cb) =>
|
|
||||||
cb(event.projectName)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (event.type === 'RunTaskClick') {
|
|
||||||
this.runTaskCallbackListeners.forEach((cb) => cb(event.taskId));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
focusProject(projectName: string) {
|
|
||||||
this.router.navigate(`/projects/${encodeURIComponent(projectName)}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
selectAllProjects() {
|
|
||||||
this.router.navigate(`/projects/all`);
|
|
||||||
}
|
|
||||||
|
|
||||||
enableExperimentalFeatures() {
|
|
||||||
localStorage.setItem('showExperimentalFeatures', 'true');
|
|
||||||
window.appConfig.showExperimentalFeatures = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
disableExperimentalFeatures() {
|
|
||||||
localStorage.setItem('showExperimentalFeatures', 'false');
|
|
||||||
window.appConfig.showExperimentalFeatures = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
registerFileClickCallback(callback: (url: string) => void) {
|
|
||||||
this.fileClickCallbackListeners.push(callback);
|
|
||||||
}
|
|
||||||
registerOpenProjectConfigCallback(callback: (projectName: string) => void) {
|
|
||||||
this.openProjectConfigCallbackListeners.push(callback);
|
|
||||||
}
|
|
||||||
registerRunTaskCallback(callback: (taskId: string) => void) {
|
|
||||||
this.runTaskCallbackListeners.push(callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleLegacyProjectGraphEvent(event: ProjectGraphMachineEvents) {
|
|
||||||
switch (event.type) {
|
|
||||||
case 'focusProject':
|
|
||||||
this.focusProject(event.projectName);
|
|
||||||
break;
|
|
||||||
case 'selectAll':
|
|
||||||
this.selectAllProjects();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
this._graphIsReady.then(() => this._projectGraphService.send(event));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -7,7 +7,7 @@ import type {
|
|||||||
/* eslint-enable @nx/enforce-module-boundaries */
|
/* eslint-enable @nx/enforce-module-boundaries */
|
||||||
import { interpret } from 'xstate';
|
import { interpret } from 'xstate';
|
||||||
import { projectGraphMachine } from './project-graph.machine';
|
import { projectGraphMachine } from './project-graph.machine';
|
||||||
import { AppConfig } from '../../interfaces';
|
import { AppConfig } from '@nx/graph/shared';
|
||||||
|
|
||||||
export const mockProjects: ProjectGraphProjectNode[] = [
|
export const mockProjects: ProjectGraphProjectNode[] = [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -15,15 +15,12 @@ import {
|
|||||||
selectedProjectNamesSelector,
|
selectedProjectNamesSelector,
|
||||||
workspaceLayoutSelector,
|
workspaceLayoutSelector,
|
||||||
} from './machines/selectors';
|
} from './machines/selectors';
|
||||||
import {
|
import { getProjectsByType, parseParentDirectoriesFromFilePath } from '../util';
|
||||||
getProjectsByType,
|
|
||||||
parseParentDirectoriesFromFilePath,
|
|
||||||
useRouteConstructor,
|
|
||||||
} from '../util';
|
|
||||||
import { ExperimentalFeature } from '../ui-components/experimental-feature';
|
import { ExperimentalFeature } from '../ui-components/experimental-feature';
|
||||||
import { TracingAlgorithmType } from './machines/interfaces';
|
import { TracingAlgorithmType } from './machines/interfaces';
|
||||||
import { getProjectGraphService } from '../machines/get-services';
|
import { getProjectGraphService } from '../machines/get-services';
|
||||||
import { Link, useNavigate } from 'react-router-dom';
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
|
import { useRouteConstructor } from '@nx/graph/shared';
|
||||||
|
|
||||||
interface SidebarProject {
|
interface SidebarProject {
|
||||||
projectGraphNode: ProjectGraphNode;
|
projectGraphNode: ProjectGraphNode;
|
||||||
|
|||||||
@ -1,6 +1,11 @@
|
|||||||
import { useCallback, useEffect } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
import { useIntervalWhen } from '../hooks/use-interval-when';
|
||||||
|
import { getProjectGraphService } from '../machines/get-services';
|
||||||
import { ExperimentalFeature } from '../ui-components/experimental-feature';
|
import { ExperimentalFeature } from '../ui-components/experimental-feature';
|
||||||
|
import { FocusedPanel } from '../ui-components/focused-panel';
|
||||||
|
import { ShowHideAll } from '../ui-components/show-hide-all';
|
||||||
import { useProjectGraphSelector } from './hooks/use-project-graph-selector';
|
import { useProjectGraphSelector } from './hooks/use-project-graph-selector';
|
||||||
|
import { TracingAlgorithmType } from './machines/interfaces';
|
||||||
import {
|
import {
|
||||||
collapseEdgesSelector,
|
collapseEdgesSelector,
|
||||||
focusedProjectNameSelector,
|
focusedProjectNameSelector,
|
||||||
@ -12,21 +17,17 @@ import {
|
|||||||
textFilterSelector,
|
textFilterSelector,
|
||||||
} from './machines/selectors';
|
} from './machines/selectors';
|
||||||
import { CollapseEdgesPanel } from './panels/collapse-edges-panel';
|
import { CollapseEdgesPanel } from './panels/collapse-edges-panel';
|
||||||
import { FocusedPanel } from '../ui-components/focused-panel';
|
|
||||||
import { GroupByFolderPanel } from './panels/group-by-folder-panel';
|
import { GroupByFolderPanel } from './panels/group-by-folder-panel';
|
||||||
import { ProjectList } from './project-list';
|
|
||||||
import { SearchDepth } from './panels/search-depth';
|
import { SearchDepth } from './panels/search-depth';
|
||||||
import { ShowHideAll } from '../ui-components/show-hide-all';
|
|
||||||
import { TextFilterPanel } from './panels/text-filter-panel';
|
import { TextFilterPanel } from './panels/text-filter-panel';
|
||||||
import { TracingPanel } from './panels/tracing-panel';
|
import { TracingPanel } from './panels/tracing-panel';
|
||||||
import { useEnvironmentConfig } from '../hooks/use-environment-config';
|
import { ProjectList } from './project-list';
|
||||||
import { TracingAlgorithmType } from './machines/interfaces';
|
|
||||||
import { getProjectGraphService } from '../machines/get-services';
|
|
||||||
import { useIntervalWhen } from '../hooks/use-interval-when';
|
|
||||||
/* eslint-disable @nx/enforce-module-boundaries */
|
/* eslint-disable @nx/enforce-module-boundaries */
|
||||||
// nx-ignore-next-line
|
// nx-ignore-next-line
|
||||||
import { ProjectGraphClientResponse } from 'nx/src/command-line/graph/graph';
|
import { ProjectGraphClientResponse } from 'nx/src/command-line/graph/graph';
|
||||||
/* eslint-enable @nx/enforce-module-boundaries */
|
/* eslint-enable @nx/enforce-module-boundaries */
|
||||||
|
import { useFloating } from '@floating-ui/react';
|
||||||
|
import { useEnvironmentConfig, useRouteConstructor } from '@nx/graph/shared';
|
||||||
import {
|
import {
|
||||||
useNavigate,
|
useNavigate,
|
||||||
useParams,
|
useParams,
|
||||||
@ -35,7 +36,7 @@ import {
|
|||||||
} from 'react-router-dom';
|
} from 'react-router-dom';
|
||||||
import { getProjectGraphDataService } from '../hooks/get-project-graph-data-service';
|
import { getProjectGraphDataService } from '../hooks/get-project-graph-data-service';
|
||||||
import { useCurrentPath } from '../hooks/use-current-path';
|
import { useCurrentPath } from '../hooks/use-current-path';
|
||||||
import { useRouteConstructor } from '../util';
|
import { ProjectDetailsModal } from '../ui-components/project-details-modal';
|
||||||
|
|
||||||
export function ProjectsSidebar(): JSX.Element {
|
export function ProjectsSidebar(): JSX.Element {
|
||||||
const environmentConfig = useEnvironmentConfig();
|
const environmentConfig = useEnvironmentConfig();
|
||||||
@ -329,6 +330,8 @@ export function ProjectsSidebar(): JSX.Element {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<ProjectDetailsModal />
|
||||||
|
|
||||||
{focusedProject ? (
|
{focusedProject ? (
|
||||||
<FocusedPanel
|
<FocusedPanel
|
||||||
focusedLabel={focusedProject}
|
focusedLabel={focusedProject}
|
||||||
|
|||||||
@ -16,12 +16,11 @@ import { useEffect, useMemo } from 'react';
|
|||||||
import { getGraphService } from '../machines/graph.service';
|
import { getGraphService } from '../machines/graph.service';
|
||||||
import { CheckboxPanel } from '../ui-components/checkbox-panel';
|
import { CheckboxPanel } from '../ui-components/checkbox-panel';
|
||||||
|
|
||||||
|
import { useRouteConstructor } from '@nx/graph/shared';
|
||||||
import { Dropdown } from '@nx/graph/ui-components';
|
import { Dropdown } from '@nx/graph/ui-components';
|
||||||
import { useCurrentPath } from '../hooks/use-current-path';
|
import { useCurrentPath } from '../hooks/use-current-path';
|
||||||
import { ShowHideAll } from '../ui-components/show-hide-all';
|
import { ShowHideAll } from '../ui-components/show-hide-all';
|
||||||
import { createTaskName, useRouteConstructor } from '../util';
|
import { createTaskName } from '../util';
|
||||||
import { GraphInteractionEvents } from '@nx/graph/ui-graph';
|
|
||||||
import { getProjectGraphDataService } from '../hooks/get-project-graph-data-service';
|
|
||||||
|
|
||||||
export function TasksSidebar() {
|
export function TasksSidebar() {
|
||||||
const graphService = getGraphService();
|
const graphService = getGraphService();
|
||||||
|
|||||||
@ -1,15 +1,17 @@
|
|||||||
import { createBrowserRouter, createHashRouter } from 'react-router-dom';
|
import { createBrowserRouter, createHashRouter } from 'react-router-dom';
|
||||||
import { getRoutesForEnvironment } from './routes';
|
import { getRoutesForEnvironment } from './routes';
|
||||||
import { getEnvironmentConfig } from './hooks/use-environment-config';
|
import { getEnvironmentConfig } from '@nx/graph/shared';
|
||||||
|
|
||||||
let router;
|
let router;
|
||||||
|
|
||||||
export function getRouter() {
|
export function getRouter() {
|
||||||
if (!router) {
|
if (!router) {
|
||||||
const environmentConfig = getEnvironmentConfig();
|
const environmentConfig = getEnvironmentConfig();
|
||||||
|
|
||||||
let routerCreate = createBrowserRouter;
|
let routerCreate = createBrowserRouter;
|
||||||
if (environmentConfig.localMode === 'build') {
|
if (
|
||||||
|
environmentConfig.localMode === 'build' ||
|
||||||
|
environmentConfig.environment === 'nx-console'
|
||||||
|
) {
|
||||||
routerCreate = createHashRouter;
|
routerCreate = createHashRouter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { FetchProjectGraphService } from '../fetch-project-graph-service';
|
|||||||
import { ProjectGraphService } from '../interfaces';
|
import { ProjectGraphService } from '../interfaces';
|
||||||
import { LocalProjectGraphService } from '../local-project-graph-service';
|
import { LocalProjectGraphService } from '../local-project-graph-service';
|
||||||
import { MockProjectGraphService } from '../mock-project-graph-service';
|
import { MockProjectGraphService } from '../mock-project-graph-service';
|
||||||
|
import { NxConsoleProjectGraphService } from '../nx-console-project-graph-service';
|
||||||
|
|
||||||
let projectGraphService: ProjectGraphService;
|
let projectGraphService: ProjectGraphService;
|
||||||
|
|
||||||
@ -11,10 +12,9 @@ export function getProjectGraphDataService() {
|
|||||||
projectGraphService = new FetchProjectGraphService();
|
projectGraphService = new FetchProjectGraphService();
|
||||||
} else if (window.environment === 'watch') {
|
} else if (window.environment === 'watch') {
|
||||||
projectGraphService = new MockProjectGraphService();
|
projectGraphService = new MockProjectGraphService();
|
||||||
} else if (
|
} else if (window.environment === 'nx-console') {
|
||||||
window.environment === 'release' ||
|
projectGraphService = new NxConsoleProjectGraphService();
|
||||||
window.environment === 'nx-console'
|
} else if (window.environment === 'release') {
|
||||||
) {
|
|
||||||
if (window.localMode === 'build') {
|
if (window.localMode === 'build') {
|
||||||
projectGraphService = new LocalProjectGraphService();
|
projectGraphService = new LocalProjectGraphService();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { matchRoutes, useLocation } from 'react-router-dom';
|
import { matchRoutes, useLocation } from 'react-router-dom';
|
||||||
import { getRoutesForEnvironment } from '../routes';
|
import { getRoutesForEnvironment } from '../routes';
|
||||||
import { getEnvironmentConfig } from './use-environment-config';
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { getEnvironmentConfig } from '@nx/graph/shared';
|
||||||
|
|
||||||
export const useCurrentPath = () => {
|
export const useCurrentPath = () => {
|
||||||
const [lastLocation, setLastLocation] = useState<string>();
|
const [lastLocation, setLastLocation] = useState<string>();
|
||||||
|
|||||||
@ -6,15 +6,6 @@ import type {
|
|||||||
} from 'nx/src/command-line/graph/graph';
|
} from 'nx/src/command-line/graph/graph';
|
||||||
/* eslint-enable @nx/enforce-module-boundaries */
|
/* eslint-enable @nx/enforce-module-boundaries */
|
||||||
|
|
||||||
export interface WorkspaceData {
|
|
||||||
id: string;
|
|
||||||
label: string;
|
|
||||||
projectGraphUrl: string;
|
|
||||||
taskGraphUrl: string;
|
|
||||||
taskInputsUrl: string;
|
|
||||||
sourceMapsUrl: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WorkspaceLayout {
|
export interface WorkspaceLayout {
|
||||||
libsDir: string;
|
libsDir: string;
|
||||||
appsDir: string;
|
appsDir: string;
|
||||||
@ -35,13 +26,6 @@ export interface Environment {
|
|||||||
environment: 'dev' | 'watch' | 'release';
|
environment: 'dev' | 'watch' | 'release';
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AppConfig {
|
|
||||||
showDebugger: boolean;
|
|
||||||
showExperimentalFeatures: boolean;
|
|
||||||
workspaces: WorkspaceData[];
|
|
||||||
defaultWorkspaceId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GraphPerfReport {
|
export interface GraphPerfReport {
|
||||||
renderTime: number;
|
renderTime: number;
|
||||||
numNodes: number;
|
numNodes: number;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { GraphService } from '@nx/graph/ui-graph';
|
import { GraphService } from '@nx/graph/ui-graph';
|
||||||
import { selectValueByThemeStatic } from '../theme-resolver';
|
import { selectValueByThemeStatic } from '../theme-resolver';
|
||||||
import { getEnvironmentConfig } from '../hooks/use-environment-config';
|
|
||||||
import { getProjectGraphDataService } from '../hooks/get-project-graph-data-service';
|
import { getProjectGraphDataService } from '../hooks/get-project-graph-data-service';
|
||||||
|
import { getEnvironmentConfig } from '@nx/graph/shared';
|
||||||
|
|
||||||
let graphService: GraphService;
|
let graphService: GraphService;
|
||||||
|
|
||||||
|
|||||||
34
graph/client/src/app/nx-console-project-graph-service.ts
Normal file
34
graph/client/src/app/nx-console-project-graph-service.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
/* eslint-disable @nx/enforce-module-boundaries */
|
||||||
|
// nx-ignore-next-line
|
||||||
|
import type {
|
||||||
|
ProjectGraphClientResponse,
|
||||||
|
TaskGraphClientResponse,
|
||||||
|
} from 'nx/src/command-line/graph/graph';
|
||||||
|
import { ProjectGraphService } from './interfaces';
|
||||||
|
|
||||||
|
export class NxConsoleProjectGraphService implements ProjectGraphService {
|
||||||
|
async getHash(): Promise<string> {
|
||||||
|
return new Promise((resolve) => resolve('some-hash'));
|
||||||
|
}
|
||||||
|
|
||||||
|
async getProjectGraph(url: string): Promise<ProjectGraphClientResponse> {
|
||||||
|
return await window.externalApi.loadProjectGraph?.(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getTaskGraph(url: string): Promise<TaskGraphClientResponse> {
|
||||||
|
return await window.externalApi.loadTaskGraph?.(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getExpandedTaskInputs(
|
||||||
|
taskId: string
|
||||||
|
): Promise<Record<string, string[]>> {
|
||||||
|
const res = await window.externalApi.loadExpandedTaskInputs?.(taskId);
|
||||||
|
return res ? res[taskId] : {};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSourceMaps(
|
||||||
|
url: string
|
||||||
|
): Promise<Record<string, Record<string, string[]>>> {
|
||||||
|
return await window.externalApi.loadSourceMaps?.(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,15 +1,15 @@
|
|||||||
import { Shell } from './shell';
|
|
||||||
import { redirect, RouteObject } from 'react-router-dom';
|
import { redirect, RouteObject } from 'react-router-dom';
|
||||||
import { ProjectsSidebar } from './feature-projects/projects-sidebar';
|
import { ProjectsSidebar } from './feature-projects/projects-sidebar';
|
||||||
import { TasksSidebar } from './feature-tasks/tasks-sidebar';
|
import { TasksSidebar } from './feature-tasks/tasks-sidebar';
|
||||||
import { getEnvironmentConfig } from './hooks/use-environment-config';
|
import { Shell } from './shell';
|
||||||
/* eslint-disable @nx/enforce-module-boundaries */
|
/* eslint-disable @nx/enforce-module-boundaries */
|
||||||
// nx-ignore-next-line
|
// nx-ignore-next-line
|
||||||
import { ProjectGraphClientResponse } from 'nx/src/command-line/graph/graph';
|
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 { ProjectDetailsPage } from '@nx/graph/project-details';
|
||||||
|
import { getEnvironmentConfig } from '@nx/graph/shared';
|
||||||
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';
|
import { getProjectGraphDataService } from './hooks/get-project-graph-data-service';
|
||||||
|
|
||||||
const { appConfig } = getEnvironmentConfig();
|
const { appConfig } = getEnvironmentConfig();
|
||||||
const projectGraphDataService = getProjectGraphDataService();
|
const projectGraphDataService = getProjectGraphDataService();
|
||||||
@ -44,7 +44,9 @@ const workspaceDataLoader = async (selectedWorkspaceId: string) => {
|
|||||||
|
|
||||||
const targets = Array.from(targetsSet).sort((a, b) => a.localeCompare(b));
|
const targets = Array.from(targetsSet).sort((a, b) => a.localeCompare(b));
|
||||||
|
|
||||||
return { ...projectGraph, targets };
|
const sourceMaps = await sourceMapsLoader(selectedWorkspaceId);
|
||||||
|
|
||||||
|
return { ...projectGraph, targets, sourceMaps };
|
||||||
};
|
};
|
||||||
|
|
||||||
const taskDataLoader = async (selectedWorkspaceId: string) => {
|
const taskDataLoader = async (selectedWorkspaceId: string) => {
|
||||||
@ -176,7 +178,7 @@ export const devRoutes: RouteObject[] = [
|
|||||||
{
|
{
|
||||||
path: ':selectedWorkspaceId/project-details/:projectName',
|
path: ':selectedWorkspaceId/project-details/:projectName',
|
||||||
id: 'selectedProjectDetails',
|
id: 'selectedProjectDetails',
|
||||||
element: <ProjectDetails />,
|
element: <ProjectDetailsPage />,
|
||||||
loader: async ({ request, params }) => {
|
loader: async ({ request, params }) => {
|
||||||
const projectName = params.projectName;
|
const projectName = params.projectName;
|
||||||
return projectDetailsLoader(params.selectedWorkspaceId, projectName);
|
return projectDetailsLoader(params.selectedWorkspaceId, projectName);
|
||||||
@ -213,7 +215,7 @@ export const releaseRoutes: RouteObject[] = [
|
|||||||
{
|
{
|
||||||
path: 'project-details/:projectName',
|
path: 'project-details/:projectName',
|
||||||
id: 'selectedProjectDetails',
|
id: 'selectedProjectDetails',
|
||||||
element: <ProjectDetails />,
|
element: <ProjectDetailsPage />,
|
||||||
loader: async ({ request, params }) => {
|
loader: async ({ request, params }) => {
|
||||||
const projectName = params.projectName;
|
const projectName = params.projectName;
|
||||||
return projectDetailsLoader(appConfig.defaultWorkspaceId, projectName);
|
return projectDetailsLoader(appConfig.defaultWorkspaceId, projectName);
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import {
|
|||||||
} from '@heroicons/react/24/outline';
|
} from '@heroicons/react/24/outline';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { DebuggerPanel } from './ui-components/debugger-panel';
|
import { DebuggerPanel } from './ui-components/debugger-panel';
|
||||||
import { useEnvironmentConfig } from './hooks/use-environment-config';
|
|
||||||
import { getGraphService } from './machines/graph.service';
|
import { getGraphService } from './machines/graph.service';
|
||||||
import { Outlet, useNavigate, useParams } from 'react-router-dom';
|
import { Outlet, useNavigate, useParams } from 'react-router-dom';
|
||||||
import { ThemePanel } from './feature-projects/panels/theme-panel';
|
import { ThemePanel } from './feature-projects/panels/theme-panel';
|
||||||
@ -17,6 +16,7 @@ import { getProjectGraphService } from './machines/get-services';
|
|||||||
import { useSyncExternalStore } from 'use-sync-external-store/shim';
|
import { useSyncExternalStore } from 'use-sync-external-store/shim';
|
||||||
import { Tooltip } from '@nx/graph/ui-tooltips';
|
import { Tooltip } from '@nx/graph/ui-tooltips';
|
||||||
import { TooltipDisplay } from './ui-tooltips/graph-tooltip-display';
|
import { TooltipDisplay } from './ui-tooltips/graph-tooltip-display';
|
||||||
|
import { useEnvironmentConfig } from '@nx/graph/shared';
|
||||||
|
|
||||||
export function Shell(): JSX.Element {
|
export function Shell(): JSX.Element {
|
||||||
const projectGraphService = getProjectGraphService();
|
const projectGraphService = getProjectGraphService();
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { WorkspaceData, GraphPerfReport } from '../interfaces';
|
import { GraphPerfReport } from '../interfaces';
|
||||||
import { Dropdown } from '@nx/graph/ui-components';
|
import { Dropdown } from '@nx/graph/ui-components';
|
||||||
|
import type { WorkspaceData } from '@nx/graph/shared';
|
||||||
|
|
||||||
export interface DebuggerPanelProps {
|
export interface DebuggerPanelProps {
|
||||||
projects: WorkspaceData[];
|
projects: WorkspaceData[];
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEnvironmentConfig } from '../hooks/use-environment-config';
|
import { useEnvironmentConfig } from '@nx/graph/shared';
|
||||||
import { Children, cloneElement } from 'react';
|
import { Children, cloneElement } from 'react';
|
||||||
|
|
||||||
export function ExperimentalFeature(props) {
|
export function ExperimentalFeature(props) {
|
||||||
|
|||||||
67
graph/client/src/app/ui-components/project-details-modal.tsx
Normal file
67
graph/client/src/app/ui-components/project-details-modal.tsx
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
/* eslint-disable @nx/enforce-module-boundaries */
|
||||||
|
// nx-ignore-next-line
|
||||||
|
import { useFloating } from '@floating-ui/react';
|
||||||
|
import { XMarkIcon } from '@heroicons/react/24/outline';
|
||||||
|
import { ProjectDetails } from '@nx/graph/project-details';
|
||||||
|
/* eslint-disable @nx/enforce-module-boundaries */
|
||||||
|
// nx-ignore-next-line
|
||||||
|
import { ProjectGraphClientResponse } from 'nx/src/command-line/graph/graph';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useRouteLoaderData, useSearchParams } from 'react-router-dom';
|
||||||
|
|
||||||
|
export function ProjectDetailsModal() {
|
||||||
|
const workspaceData = useRouteLoaderData(
|
||||||
|
'selectedWorkspace'
|
||||||
|
) as ProjectGraphClientResponse & { sourceMaps: string[] };
|
||||||
|
const [project, setProject] = useState(null);
|
||||||
|
const [sourceMap, setSourceMap] = useState(null);
|
||||||
|
|
||||||
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const { refs } = useFloating({
|
||||||
|
open: isOpen,
|
||||||
|
strategy: 'fixed',
|
||||||
|
placement: 'right',
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (searchParams.has('projectDetails')) {
|
||||||
|
const projectName = searchParams.get('projectDetails');
|
||||||
|
const project = workspaceData.projects.find(
|
||||||
|
(project) => project.name === projectName
|
||||||
|
);
|
||||||
|
if (!project) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const sourceMap = workspaceData.sourceMaps[project.data.root];
|
||||||
|
setProject(project);
|
||||||
|
setSourceMap(sourceMap);
|
||||||
|
setIsOpen(true);
|
||||||
|
}
|
||||||
|
}, [searchParams, workspaceData]);
|
||||||
|
|
||||||
|
function onClose() {
|
||||||
|
searchParams.delete('projectDetails');
|
||||||
|
setSearchParams(searchParams);
|
||||||
|
setIsOpen(false);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
isOpen && (
|
||||||
|
<div
|
||||||
|
className="top-24 z-20 right-4 opacity-100 bg-white dark:bg-slate-800 fixed h-max w-1/3"
|
||||||
|
style={{
|
||||||
|
height: 'calc(100vh - 6rem - 2rem)',
|
||||||
|
}}
|
||||||
|
ref={refs.setFloating}
|
||||||
|
>
|
||||||
|
<div className="rounded-md h-full border border-slate-500">
|
||||||
|
<ProjectDetails project={project} sourceMap={sourceMap} />
|
||||||
|
<div className="top-2 right-2 absolute" onClick={onClose}>
|
||||||
|
<XMarkIcon className="h-4 w-4" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,9 +1,9 @@
|
|||||||
import { ProjectNodeToolTipProps } from '@nx/graph/ui-tooltips';
|
import { ProjectNodeToolTipProps } from '@nx/graph/ui-tooltips';
|
||||||
import { getProjectGraphService } from '../machines/get-services';
|
import { getProjectGraphService } from '../machines/get-services';
|
||||||
import { useRouteConstructor } from '../util';
|
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
import { TooltipButton, TooltipLinkButton } from '@nx/graph/ui-tooltips';
|
import { TooltipButton, TooltipLinkButton } from '@nx/graph/ui-tooltips';
|
||||||
import { FlagIcon, MapPinIcon } from '@heroicons/react/24/solid';
|
import { FlagIcon, MapPinIcon } from '@heroicons/react/24/solid';
|
||||||
|
import { useRouteConstructor } from '@nx/graph/shared';
|
||||||
|
|
||||||
export function ProjectNodeActions({ id }: ProjectNodeToolTipProps) {
|
export function ProjectNodeActions({ id }: ProjectNodeToolTipProps) {
|
||||||
const projectGraphService = getProjectGraphService();
|
const projectGraphService = getProjectGraphService();
|
||||||
@ -12,7 +12,11 @@ export function ProjectNodeActions({ id }: ProjectNodeToolTipProps) {
|
|||||||
const routeConstructor = useRouteConstructor();
|
const routeConstructor = useRouteConstructor();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const encodedId = encodeURIComponent(id);
|
const encodedId = encodeURIComponent(id);
|
||||||
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
|
||||||
|
function onProjectDetails() {
|
||||||
|
setSearchParams({ projectDetails: id });
|
||||||
|
}
|
||||||
function onExclude() {
|
function onExclude() {
|
||||||
projectGraphService.send({
|
projectGraphService.send({
|
||||||
type: 'deselectProject',
|
type: 'deselectProject',
|
||||||
@ -36,6 +40,7 @@ export function ProjectNodeActions({ id }: ProjectNodeToolTipProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-3 gap-4">
|
<div className="grid grid-cols-3 gap-4">
|
||||||
|
{/* <TooltipButton onClick={onProjectDetails}>Project Details</TooltipButton> */}
|
||||||
<TooltipLinkButton to={routeConstructor(`/projects/${encodedId}`, true)}>
|
<TooltipLinkButton to={routeConstructor(`/projects/${encodedId}`, true)}>
|
||||||
Focus
|
Focus
|
||||||
</TooltipLinkButton>
|
</TooltipLinkButton>
|
||||||
|
|||||||
@ -1,49 +1,8 @@
|
|||||||
/* eslint-disable @nx/enforce-module-boundaries */
|
/* eslint-disable @nx/enforce-module-boundaries */
|
||||||
// nx-ignore-next-line
|
// nx-ignore-next-line
|
||||||
import { ProjectGraphDependency, ProjectGraphProjectNode } from '@nx/devkit';
|
import { ProjectGraphDependency, ProjectGraphProjectNode } from '@nx/devkit';
|
||||||
|
import { getEnvironmentConfig } from '@nx/graph/shared';
|
||||||
/* eslint-enable @nx/enforce-module-boundaries */
|
/* eslint-enable @nx/enforce-module-boundaries */
|
||||||
import { getEnvironmentConfig } from './hooks/use-environment-config';
|
|
||||||
import { To, useParams, useSearchParams } from 'react-router-dom';
|
|
||||||
|
|
||||||
export const useRouteConstructor = (): ((
|
|
||||||
to: To,
|
|
||||||
retainSearchParams: boolean
|
|
||||||
) => To) => {
|
|
||||||
const { environment } = getEnvironmentConfig();
|
|
||||||
const { selectedWorkspaceId } = useParams();
|
|
||||||
const [searchParams] = useSearchParams();
|
|
||||||
|
|
||||||
return (to: To, retainSearchParams: true) => {
|
|
||||||
let pathname = '';
|
|
||||||
|
|
||||||
if (typeof to === 'object') {
|
|
||||||
if (environment === 'dev') {
|
|
||||||
pathname = `/${selectedWorkspaceId}${to.pathname}`;
|
|
||||||
} else {
|
|
||||||
pathname = to.pathname;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...to,
|
|
||||||
pathname,
|
|
||||||
search: to.search
|
|
||||||
? to.search.toString()
|
|
||||||
: retainSearchParams
|
|
||||||
? searchParams.toString()
|
|
||||||
: '',
|
|
||||||
};
|
|
||||||
} else if (typeof to === 'string') {
|
|
||||||
if (environment === 'dev') {
|
|
||||||
pathname = `/${selectedWorkspaceId}${to}`;
|
|
||||||
} else {
|
|
||||||
pathname = to;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
pathname,
|
|
||||||
search: retainSearchParams ? searchParams.toString() : '',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export function parseParentDirectoriesFromFilePath(
|
export function parseParentDirectoriesFromFilePath(
|
||||||
path: string,
|
path: string,
|
||||||
|
|||||||
12
graph/client/src/globals.d.ts
vendored
12
graph/client/src/globals.d.ts
vendored
@ -5,29 +5,23 @@ import type {
|
|||||||
ProjectGraphClientResponse,
|
ProjectGraphClientResponse,
|
||||||
TaskGraphClientResponse,
|
TaskGraphClientResponse,
|
||||||
} from 'nx/src/command-line/graph/graph';
|
} from 'nx/src/command-line/graph/graph';
|
||||||
/* eslint-enable @nx/enforce-module-boundaries */
|
import { AppConfig, ExternalApi } from '@nx/graph/shared';
|
||||||
import { AppConfig } from './app/interfaces';
|
|
||||||
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 {
|
interface Window {
|
||||||
exclude: string[];
|
exclude: string[];
|
||||||
watch: boolean;
|
watch: boolean;
|
||||||
localMode: 'serve' | 'build';
|
localMode: 'serve' | 'build';
|
||||||
projectGraphResponse?: ProjectGraphClientResponse;
|
projectGraphResponse?: ProjectGraphClientResponse;
|
||||||
taskGraphResponse?: TaskGraphClientResponse;
|
taskGraphResponse?: TaskGraphClientResponse;
|
||||||
expandedTaskInputsResponse?: ExpandedTaskInputsReponse;
|
expandedTaskInputsResponse?: ExpandedTaskInputsReponse;
|
||||||
sourceMapsResponse?: ConfigurationSourceMaps;
|
sourceMapsResponse?: Record<string, Record<string, string[]>>;
|
||||||
environment: 'dev' | 'watch' | 'release' | 'nx-console';
|
environment: 'dev' | 'watch' | 'release' | 'nx-console';
|
||||||
appConfig: AppConfig;
|
appConfig: AppConfig;
|
||||||
useXstateInspect: boolean;
|
useXstateInspect: boolean;
|
||||||
externalApi?: ExternalApi;
|
externalApi?: ExternalApi;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module 'cytoscape' {
|
declare module 'cytoscape' {
|
||||||
interface Core {
|
interface Core {
|
||||||
anywherePanning: Function;
|
anywherePanning: Function;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { StrictMode } from 'react';
|
import { StrictMode } from 'react';
|
||||||
import { inspect } from '@xstate/inspect';
|
import { inspect } from '@xstate/inspect';
|
||||||
import { App } from './app/app';
|
import { App } from './app/app';
|
||||||
import { ExternalApi } from './app/external-api';
|
import { ExternalApiImpl } from './app/external-api-impl';
|
||||||
import { render } from 'preact';
|
import { render } from 'preact';
|
||||||
|
|
||||||
if (window.useXstateInspect === true) {
|
if (window.useXstateInspect === true) {
|
||||||
@ -11,7 +11,7 @@ if (window.useXstateInspect === true) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
window.externalApi = new ExternalApi();
|
window.externalApi = new ExternalApiImpl();
|
||||||
const container = document.getElementById('app');
|
const container = document.getElementById('app');
|
||||||
|
|
||||||
if (!window.appConfig) {
|
if (!window.appConfig) {
|
||||||
|
|||||||
@ -1 +1,2 @@
|
|||||||
export * from './lib/project-details';
|
export * from './lib/project-details';
|
||||||
|
export * from './lib/project-details-page';
|
||||||
|
|||||||
288
graph/project-details/src/lib/json-line-renderer.tsx
Normal file
288
graph/project-details/src/lib/json-line-renderer.tsx
Normal file
@ -0,0 +1,288 @@
|
|||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
16
graph/project-details/src/lib/project-details-page.tsx
Normal file
16
graph/project-details/src/lib/project-details-page.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
/* eslint-disable @nx/enforce-module-boundaries */
|
||||||
|
// nx-ignore-next-line
|
||||||
|
import { ProjectGraphProjectNode } from '@nx/devkit';
|
||||||
|
import { useRouteLoaderData } from 'react-router-dom';
|
||||||
|
import ProjectDetails from './project-details';
|
||||||
|
|
||||||
|
export function ProjectDetailsPage() {
|
||||||
|
const { project, sourceMap } = useRouteLoaderData(
|
||||||
|
'selectedProjectDetails'
|
||||||
|
) as {
|
||||||
|
project: ProjectGraphProjectNode;
|
||||||
|
sourceMap: Record<string, string[]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
return ProjectDetails({ project, sourceMap });
|
||||||
|
}
|
||||||
@ -1,29 +1,86 @@
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// 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 { useNavigate, useRouteLoaderData } from 'react-router-dom';
|
||||||
import { useRouteLoaderData } 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';
|
||||||
|
|
||||||
export function ProjectDetails() {
|
import {
|
||||||
const {
|
getExternalApiService,
|
||||||
project: {
|
useEnvironmentConfig,
|
||||||
name,
|
useRouteConstructor,
|
||||||
data: { targets, root, ...projectData },
|
} from '@nx/graph/shared';
|
||||||
},
|
import { JsonLineRenderer } from './json-line-renderer';
|
||||||
sourceMap,
|
import { EyeIcon } from '@heroicons/react/24/outline';
|
||||||
} = useRouteLoaderData('selectedProjectDetails') as {
|
import PropertyRenderer from './property-renderer';
|
||||||
project: ProjectGraphProjectNode;
|
import Target from './target';
|
||||||
sourceMap: Record<string, string[]>;
|
|
||||||
|
export interface ProjectDetailsProps {
|
||||||
|
project: ProjectGraphProjectNode;
|
||||||
|
sourceMap: Record<string, string[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ProjectDetails({
|
||||||
|
project: {
|
||||||
|
name,
|
||||||
|
data: { root, ...projectData },
|
||||||
|
},
|
||||||
|
sourceMap,
|
||||||
|
}: ProjectDetailsProps) {
|
||||||
|
const { environment } = useEnvironmentConfig();
|
||||||
|
const externalApiService = getExternalApiService();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const routeContructor = useRouteConstructor();
|
||||||
|
|
||||||
|
const viewInProjectGraph = () => {
|
||||||
|
if (environment === 'nx-console') {
|
||||||
|
externalApiService.postEvent({
|
||||||
|
type: 'open-project-graph',
|
||||||
|
payload: {
|
||||||
|
projectName: name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
navigate(routeContructor(`/projects/${encodeURIComponent(name)}`, true));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 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">{name}</h1>
|
<h1 className="text-2xl flex items-center gap-2">
|
||||||
|
{name}{' '}
|
||||||
|
<EyeIcon className="h-5 w-5" onClick={viewInProjectGraph}></EyeIcon>
|
||||||
|
</h1>
|
||||||
<h2 className="text-lg pl-6 mb-3 flex flex-row gap-2">
|
<h2 className="text-lg pl-6 mb-3 flex flex-row gap-2">
|
||||||
{root}{' '}
|
{root}{' '}
|
||||||
{projectData.tags?.map((tag) => (
|
{projectData.tags?.map((tag) => (
|
||||||
@ -33,12 +90,14 @@ export function ProjectDetails() {
|
|||||||
<div>
|
<div>
|
||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
<h2 className="text-xl">Targets</h2>
|
<h2 className="text-xl">Targets</h2>
|
||||||
{Object.entries(targets ?? {}).map(([targetName, target]) =>
|
{Object.entries(projectData.targets ?? {}).map(
|
||||||
Target({
|
([targetName, target]) =>
|
||||||
targetName: targetName,
|
Target({
|
||||||
targetConfiguration: target,
|
projectName: name,
|
||||||
sourceMap,
|
targetName: targetName,
|
||||||
})
|
targetConfiguration: target,
|
||||||
|
sourceMap,
|
||||||
|
})
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{Object.entries(projectData).map(([key, value]) => {
|
{Object.entries(projectData).map(([key, value]) => {
|
||||||
@ -48,7 +107,8 @@ export function ProjectDetails() {
|
|||||||
key === 'name' ||
|
key === 'name' ||
|
||||||
key === '$schema' ||
|
key === '$schema' ||
|
||||||
key === 'tags' ||
|
key === 'tags' ||
|
||||||
key === 'files'
|
key === 'files' ||
|
||||||
|
key === 'sourceRoot'
|
||||||
)
|
)
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|
||||||
@ -63,4 +123,22 @@ 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;
|
||||||
|
|||||||
@ -20,14 +20,23 @@ export function PropertyRenderer(props: PropertyRendererProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div title={getSourceInformation(sourceMap, sourceMapKey)}>
|
<div
|
||||||
{isCollapsible && (
|
title={getSourceInformation(sourceMap, sourceMapKey)}
|
||||||
<button className="text-xs" onClick={toggleCollapse}>
|
className={!isCollapsible ? 'pl-4 relative' : 'relative'}
|
||||||
{isCollapsed ? '\u25B6' : '\u25BC'}
|
>
|
||||||
</button>
|
<span>
|
||||||
)}
|
{isCollapsible && (
|
||||||
<span className="font-medium">{propertyKey}</span>:{' '}
|
<button className="text-xs w-4" onClick={toggleCollapse}>
|
||||||
{renderOpening(propertyValue)}
|
{isCollapsed ? '\u25B6' : '\u25BC'}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<span className="font-medium">
|
||||||
|
{propertyKey}
|
||||||
|
<div className="absolute top-0 left-0 w-full bg-grey-500 z-10"></div>
|
||||||
|
</span>
|
||||||
|
: {renderOpening(propertyValue)}
|
||||||
|
</span>
|
||||||
|
|
||||||
{!isCollapsed || !isCollapsible ? (
|
{!isCollapsed || !isCollapsible ? (
|
||||||
<PropertyValueRenderer {...props} />
|
<PropertyValueRenderer {...props} />
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@ -1,22 +1,123 @@
|
|||||||
/* eslint-disable @nx/enforce-module-boundaries */
|
/* eslint-disable @nx/enforce-module-boundaries */
|
||||||
|
// nx-ignore-next-line
|
||||||
|
import {
|
||||||
|
EyeIcon,
|
||||||
|
PencilSquareIcon,
|
||||||
|
PlayIcon,
|
||||||
|
} from '@heroicons/react/24/outline';
|
||||||
|
|
||||||
// nx-ignore-next-line
|
// nx-ignore-next-line
|
||||||
import { TargetConfiguration } from '@nx/devkit';
|
import { TargetConfiguration } from '@nx/devkit';
|
||||||
|
import {
|
||||||
|
getExternalApiService,
|
||||||
|
useEnvironmentConfig,
|
||||||
|
useRouteConstructor,
|
||||||
|
} from '@nx/graph/shared';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
import PropertyRenderer from './property-renderer';
|
import PropertyRenderer from './property-renderer';
|
||||||
import { useState } from 'react';
|
|
||||||
|
|
||||||
/* eslint-disable-next-line */
|
/* eslint-disable-next-line */
|
||||||
export interface TargetProps {
|
export interface TargetProps {
|
||||||
|
projectName: string;
|
||||||
targetName: string;
|
targetName: string;
|
||||||
targetConfiguration: TargetConfiguration;
|
targetConfiguration: TargetConfiguration;
|
||||||
sourceMap: Record<string, string[]>;
|
sourceMap: Record<string, string[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Target(props: TargetProps) {
|
export function Target(props: TargetProps) {
|
||||||
|
const { environment } = useEnvironmentConfig();
|
||||||
|
const externalApiService = getExternalApiService();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const routeContructor = useRouteConstructor();
|
||||||
|
|
||||||
|
const runTarget = () => {
|
||||||
|
externalApiService.postEvent({
|
||||||
|
type: 'run-task',
|
||||||
|
payload: { taskId: `${props.projectName}:${props.targetName}` },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const viewInTaskGraph = () => {
|
||||||
|
if (environment === 'nx-console') {
|
||||||
|
externalApiService.postEvent({
|
||||||
|
type: 'open-task-graph',
|
||||||
|
payload: {
|
||||||
|
projectName: props.projectName,
|
||||||
|
targetName: props.targetName,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
navigate(
|
||||||
|
routeContructor(
|
||||||
|
{
|
||||||
|
pathname: `/tasks/${encodeURIComponent(props.targetName)}`,
|
||||||
|
search: `?projects=${encodeURIComponent(props.projectName)}`,
|
||||||
|
},
|
||||||
|
true
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const overrideTarget = () => {
|
||||||
|
externalApiService.postEvent({
|
||||||
|
type: 'override-target',
|
||||||
|
payload: {
|
||||||
|
projectName: props.projectName,
|
||||||
|
targetName: props.targetName,
|
||||||
|
targetConfigString: JSON.stringify(props.targetConfiguration),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const shouldDisplayOverrideTarget = () => {
|
||||||
|
return (
|
||||||
|
environment === 'nx-console' &&
|
||||||
|
Object.entries(props.sourceMap ?? {})
|
||||||
|
.filter(([key]) => key.startsWith(`targets.${props.targetName}`))
|
||||||
|
.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">
|
||||||
<h3 className="text-lg font-bold">{props.targetName}</h3>
|
<h3 className="text-lg font-bold flex items-center gap-2">
|
||||||
|
{props.targetName}{' '}
|
||||||
|
{environment === 'nx-console' && (
|
||||||
|
<PlayIcon className="h-5 w-5" onClick={runTarget} />
|
||||||
|
)}
|
||||||
|
<EyeIcon className="h-5 w-5" onClick={viewInTaskGraph}></EyeIcon>
|
||||||
|
{shouldDisplayOverrideTarget() && (
|
||||||
|
<PencilSquareIcon className="h-5 w-5" onClick={overrideTarget} />
|
||||||
|
)}
|
||||||
|
</h3>
|
||||||
<div className="ml-3">
|
<div className="ml-3">
|
||||||
{Object.entries(props.targetConfiguration).map(([key, value]) =>
|
{targetConfigurationSortedAndFiltered.map(([key, value]) =>
|
||||||
PropertyRenderer({
|
PropertyRenderer({
|
||||||
propertyKey: key,
|
propertyKey: key,
|
||||||
propertyValue: value,
|
propertyValue: value,
|
||||||
|
|||||||
21
graph/project-details/src/lib/use-map-state.ts
Normal file
21
graph/project-details/src/lib/use-map-state.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
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;
|
||||||
@ -4,7 +4,6 @@
|
|||||||
"outDir": "../../dist/out-tsc",
|
"outDir": "../../dist/out-tsc",
|
||||||
"types": [
|
"types": [
|
||||||
"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"
|
||||||
]
|
]
|
||||||
|
|||||||
12
graph/shared/.babelrc
Normal file
12
graph/shared/.babelrc
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"presets": [
|
||||||
|
[
|
||||||
|
"@nx/react/babel",
|
||||||
|
{
|
||||||
|
"runtime": "automatic",
|
||||||
|
"useBuiltIns": "usage"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"plugins": []
|
||||||
|
}
|
||||||
18
graph/shared/.eslintrc.json
Normal file
18
graph/shared/.eslintrc.json
Normal 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": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
7
graph/shared/README.md
Normal file
7
graph/shared/README.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# graph-shared
|
||||||
|
|
||||||
|
This library was generated with [Nx](https://nx.dev).
|
||||||
|
|
||||||
|
## Running unit tests
|
||||||
|
|
||||||
|
Run `nx test graph-shared` to execute the unit tests via [Jest](https://jestjs.io).
|
||||||
9
graph/shared/jest.config.ts
Normal file
9
graph/shared/jest.config.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
export default {
|
||||||
|
displayName: 'graph-shared',
|
||||||
|
preset: '../../jest.preset.js',
|
||||||
|
transform: {
|
||||||
|
'^.+\\.[tj]sx?$': 'babel-jest',
|
||||||
|
},
|
||||||
|
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
|
||||||
|
};
|
||||||
21
graph/shared/project.json
Normal file
21
graph/shared/project.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"name": "graph-shared",
|
||||||
|
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||||
|
"sourceRoot": "graph/shared/src",
|
||||||
|
"projectType": "library",
|
||||||
|
"tags": [],
|
||||||
|
"targets": {
|
||||||
|
"lint": {
|
||||||
|
"executor": "@nx/eslint:lint",
|
||||||
|
"outputs": ["{options.outputFile}"]
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"executor": "@nx/jest:jest",
|
||||||
|
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
|
||||||
|
"options": {
|
||||||
|
"jestConfig": "graph/shared/jest.config.ts",
|
||||||
|
"passWithNoTests": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
25
graph/shared/src/globals.d.ts
vendored
Normal file
25
graph/shared/src/globals.d.ts
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/* eslint-disable @nx/enforce-module-boundaries */
|
||||||
|
// nx-ignore-next-line
|
||||||
|
import type {
|
||||||
|
ExpandedTaskInputsReponse,
|
||||||
|
ProjectGraphClientResponse,
|
||||||
|
TaskGraphClientResponse,
|
||||||
|
} from 'nx/src/command-line/graph/graph';
|
||||||
|
import { AppConfig } from './lib/app-config';
|
||||||
|
import { ExternalApi } from './lib/external-api';
|
||||||
|
|
||||||
|
export declare global {
|
||||||
|
interface Window {
|
||||||
|
exclude: string[];
|
||||||
|
watch: boolean;
|
||||||
|
localMode: 'serve' | 'build';
|
||||||
|
projectGraphResponse?: ProjectGraphClientResponse;
|
||||||
|
taskGraphResponse?: TaskGraphClientResponse;
|
||||||
|
expandedTaskInputsResponse?: ExpandedTaskInputsReponse;
|
||||||
|
sourceMapsResponse?: Record<string, Record<string, string[]>>;
|
||||||
|
environment: 'dev' | 'watch' | 'release' | 'nx-console';
|
||||||
|
appConfig: AppConfig;
|
||||||
|
useXstateInspect: boolean;
|
||||||
|
externalApi?: ExternalApi;
|
||||||
|
}
|
||||||
|
}
|
||||||
5
graph/shared/src/index.ts
Normal file
5
graph/shared/src/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export * from './lib/external-api';
|
||||||
|
export * from './lib/external-api-service';
|
||||||
|
export * from './lib/use-environment-config';
|
||||||
|
export * from './lib/app-config';
|
||||||
|
export * from './lib/use-route-constructor';
|
||||||
15
graph/shared/src/lib/app-config.ts
Normal file
15
graph/shared/src/lib/app-config.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
export interface AppConfig {
|
||||||
|
showDebugger: boolean;
|
||||||
|
showExperimentalFeatures: boolean;
|
||||||
|
workspaces: WorkspaceData[];
|
||||||
|
defaultWorkspaceId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WorkspaceData {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
projectGraphUrl: string;
|
||||||
|
taskGraphUrl: string;
|
||||||
|
taskInputsUrl: string;
|
||||||
|
sourceMapsUrl: string;
|
||||||
|
}
|
||||||
24
graph/shared/src/lib/external-api-service.ts
Normal file
24
graph/shared/src/lib/external-api-service.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
let externalApiService: ExternalApiService | null = null;
|
||||||
|
|
||||||
|
export function getExternalApiService() {
|
||||||
|
if (!externalApiService) {
|
||||||
|
externalApiService = new ExternalApiService();
|
||||||
|
}
|
||||||
|
|
||||||
|
return externalApiService;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ExternalApiService {
|
||||||
|
private subscribers: Set<(event: { type: string; payload: any }) => void> =
|
||||||
|
new Set();
|
||||||
|
|
||||||
|
postEvent(event: { type: string; payload: any }) {
|
||||||
|
this.subscribers.forEach((subscriber) => {
|
||||||
|
subscriber(event);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribe(callback: (event: { type: string; payload: any }) => void) {
|
||||||
|
this.subscribers.add(callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
40
graph/shared/src/lib/external-api.ts
Normal file
40
graph/shared/src/lib/external-api.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/* eslint-disable @nx/enforce-module-boundaries */
|
||||||
|
// nx-ignore-next-line
|
||||||
|
import type {
|
||||||
|
ProjectGraphClientResponse,
|
||||||
|
TaskGraphClientResponse,
|
||||||
|
} from 'nx/src/command-line/graph/graph';
|
||||||
|
|
||||||
|
export abstract class ExternalApi {
|
||||||
|
abstract focusProject(projectName: string): void;
|
||||||
|
|
||||||
|
abstract toggleSelectProject(projectName: string): void;
|
||||||
|
|
||||||
|
abstract selectAllProjects(): void;
|
||||||
|
|
||||||
|
abstract showAffectedProjects(): void;
|
||||||
|
|
||||||
|
abstract focusTarget(projectName: string, targetName: string): void;
|
||||||
|
|
||||||
|
abstract selectAllTargetsByName(targetName: string): void;
|
||||||
|
|
||||||
|
abstract enableExperimentalFeatures(): void;
|
||||||
|
|
||||||
|
abstract disableExperimentalFeatures(): void;
|
||||||
|
|
||||||
|
loadProjectGraph:
|
||||||
|
| ((url: string) => Promise<ProjectGraphClientResponse>)
|
||||||
|
| null = null;
|
||||||
|
loadTaskGraph: ((url: string) => Promise<TaskGraphClientResponse>) | null =
|
||||||
|
null;
|
||||||
|
loadExpandedTaskInputs:
|
||||||
|
| ((taskId: string) => Promise<Record<string, Record<string, string[]>>>)
|
||||||
|
| null = null;
|
||||||
|
loadSourceMaps:
|
||||||
|
| ((url: string) => Promise<Record<string, Record<string, string[]>>>)
|
||||||
|
| null = null;
|
||||||
|
|
||||||
|
graphInteractionEventListener:
|
||||||
|
| ((event: { type: string; payload: any }) => void | undefined)
|
||||||
|
| null = null;
|
||||||
|
}
|
||||||
@ -3,7 +3,7 @@
|
|||||||
import type { ProjectGraphClientResponse } from 'nx/src/command-line/graph/graph';
|
import type { ProjectGraphClientResponse } from 'nx/src/command-line/graph/graph';
|
||||||
/* eslint-enable @nx/enforce-module-boundaries */
|
/* eslint-enable @nx/enforce-module-boundaries */
|
||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
import { AppConfig } from '../interfaces';
|
import { AppConfig } from './app-config';
|
||||||
|
|
||||||
export function useEnvironmentConfig(): {
|
export function useEnvironmentConfig(): {
|
||||||
exclude: string[];
|
exclude: string[];
|
||||||
42
graph/shared/src/lib/use-route-constructor.ts
Normal file
42
graph/shared/src/lib/use-route-constructor.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { To, useParams, useSearchParams } from 'react-router-dom';
|
||||||
|
import { getEnvironmentConfig } from './use-environment-config';
|
||||||
|
|
||||||
|
export const useRouteConstructor = (): ((
|
||||||
|
to: To,
|
||||||
|
retainSearchParams: boolean
|
||||||
|
) => To) => {
|
||||||
|
const { environment } = getEnvironmentConfig();
|
||||||
|
const { selectedWorkspaceId } = useParams();
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
|
||||||
|
return (to: To, retainSearchParams: true) => {
|
||||||
|
let pathname = '';
|
||||||
|
|
||||||
|
if (typeof to === 'object') {
|
||||||
|
if (environment === 'dev') {
|
||||||
|
pathname = `/${selectedWorkspaceId}${to.pathname}`;
|
||||||
|
} else {
|
||||||
|
pathname = to.pathname;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...to,
|
||||||
|
pathname,
|
||||||
|
search: to.search
|
||||||
|
? to.search.toString()
|
||||||
|
: retainSearchParams
|
||||||
|
? searchParams.toString()
|
||||||
|
: '',
|
||||||
|
};
|
||||||
|
} else if (typeof to === 'string') {
|
||||||
|
if (environment === 'dev') {
|
||||||
|
pathname = `/${selectedWorkspaceId}${to}`;
|
||||||
|
} else {
|
||||||
|
pathname = to;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
pathname,
|
||||||
|
search: retainSearchParams ? searchParams.toString() : '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
19
graph/shared/tsconfig.json
Normal file
19
graph/shared/tsconfig.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"allowJs": false,
|
||||||
|
"esModuleInterop": false,
|
||||||
|
"allowSyntheticDefaultImports": true
|
||||||
|
},
|
||||||
|
"files": [],
|
||||||
|
"include": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.lib.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.spec.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"extends": "../../tsconfig.base.json"
|
||||||
|
}
|
||||||
24
graph/shared/tsconfig.lib.json
Normal file
24
graph/shared/tsconfig.lib.json
Normal 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"]
|
||||||
|
},
|
||||||
|
"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"]
|
||||||
|
}
|
||||||
20
graph/shared/tsconfig.spec.json
Normal file
20
graph/shared/tsconfig.spec.json
Normal 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"
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -32,28 +32,9 @@ interface BackgroundClickEvent {
|
|||||||
type: 'BackgroundClick';
|
type: 'BackgroundClick';
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FileLinkClickEvent {
|
|
||||||
type: 'FileLinkClick';
|
|
||||||
sourceRoot: string;
|
|
||||||
file: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ProjectOpenConfigClickEvent {
|
|
||||||
type: 'ProjectOpenConfigClick';
|
|
||||||
projectName: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface RunTaskClickEvent {
|
|
||||||
type: 'RunTaskClick';
|
|
||||||
taskId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type GraphInteractionEvents =
|
export type GraphInteractionEvents =
|
||||||
| ProjectNodeClickEvent
|
| ProjectNodeClickEvent
|
||||||
| EdgeClickEvent
|
| EdgeClickEvent
|
||||||
| GraphRegeneratedEvent
|
| GraphRegeneratedEvent
|
||||||
| TaskNodeClickEvent
|
| TaskNodeClickEvent
|
||||||
| BackgroundClickEvent
|
| BackgroundClickEvent;
|
||||||
| FileLinkClickEvent
|
|
||||||
| ProjectOpenConfigClickEvent
|
|
||||||
| RunTaskClickEvent;
|
|
||||||
|
|||||||
@ -7,9 +7,11 @@ import {
|
|||||||
} from '@nx/graph/ui-tooltips';
|
} from '@nx/graph/ui-tooltips';
|
||||||
import { TooltipEvent } from './interfaces';
|
import { TooltipEvent } from './interfaces';
|
||||||
import { GraphInteractionEvents } from './graph-interaction-events';
|
import { GraphInteractionEvents } from './graph-interaction-events';
|
||||||
|
import { getExternalApiService } from '@nx/graph/shared';
|
||||||
|
|
||||||
export class GraphTooltipService {
|
export class GraphTooltipService {
|
||||||
private subscribers: Set<Function> = new Set();
|
private subscribers: Set<Function> = new Set();
|
||||||
|
private externalApiService = getExternalApiService();
|
||||||
|
|
||||||
constructor(graph: GraphService) {
|
constructor(graph: GraphService) {
|
||||||
graph.listen((event: GraphInteractionEvents) => {
|
graph.listen((event: GraphInteractionEvents) => {
|
||||||
@ -24,9 +26,11 @@ export class GraphTooltipService {
|
|||||||
const openConfigCallback =
|
const openConfigCallback =
|
||||||
graph.renderMode === 'nx-console'
|
graph.renderMode === 'nx-console'
|
||||||
? () =>
|
? () =>
|
||||||
graph.broadcast({
|
this.externalApiService.postEvent({
|
||||||
type: 'ProjectOpenConfigClick',
|
type: 'open-project-config',
|
||||||
projectName: event.data.id,
|
payload: {
|
||||||
|
projectName: event.data.id,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
: undefined;
|
: undefined;
|
||||||
this.openProjectNodeToolTip(event.ref, {
|
this.openProjectNodeToolTip(event.ref, {
|
||||||
@ -41,9 +45,11 @@ export class GraphTooltipService {
|
|||||||
const runTaskCallback =
|
const runTaskCallback =
|
||||||
graph.renderMode === 'nx-console'
|
graph.renderMode === 'nx-console'
|
||||||
? () =>
|
? () =>
|
||||||
graph.broadcast({
|
this.externalApiService.postEvent({
|
||||||
type: 'RunTaskClick',
|
type: 'run-task',
|
||||||
taskId: event.data.id,
|
payload: {
|
||||||
|
taskId: event.data.id,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
: undefined;
|
: undefined;
|
||||||
this.openTaskNodeTooltip(event.ref, {
|
this.openTaskNodeTooltip(event.ref, {
|
||||||
@ -69,10 +75,12 @@ export class GraphTooltipService {
|
|||||||
const callback =
|
const callback =
|
||||||
graph.renderMode === 'nx-console'
|
graph.renderMode === 'nx-console'
|
||||||
? (url) =>
|
? (url) =>
|
||||||
graph.broadcast({
|
this.externalApiService.postEvent({
|
||||||
type: 'FileLinkClick',
|
type: 'file-click',
|
||||||
sourceRoot: event.data.sourceRoot,
|
payload: {
|
||||||
file: url,
|
sourceRoot: event.data.sourceRoot,
|
||||||
|
file: url,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
: undefined;
|
: undefined;
|
||||||
this.openEdgeToolTip(event.ref, {
|
this.openEdgeToolTip(event.ref, {
|
||||||
|
|||||||
@ -523,6 +523,8 @@ async function startServer(
|
|||||||
currentProjectGraphClientResponse.groupByFolder = groupByFolder;
|
currentProjectGraphClientResponse.groupByFolder = groupByFolder;
|
||||||
currentProjectGraphClientResponse.exclude = exclude;
|
currentProjectGraphClientResponse.exclude = exclude;
|
||||||
|
|
||||||
|
currentSourceMapsClientResponse = sourceMapResponse;
|
||||||
|
|
||||||
const app = http.createServer(async (req, res) => {
|
const app = http.createServer(async (req, res) => {
|
||||||
// parse URL
|
// parse URL
|
||||||
const parsedUrl = new URL(req.url, `http://${host}:${port}`);
|
const parsedUrl = new URL(req.url, `http://${host}:${port}`);
|
||||||
@ -531,6 +533,8 @@ async function startServer(
|
|||||||
// e.g curl --path-as-is http://localhost:9000/../fileInDanger.txt
|
// e.g curl --path-as-is http://localhost:9000/../fileInDanger.txt
|
||||||
// by limiting the path to current directory only
|
// by limiting the path to current directory only
|
||||||
|
|
||||||
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||||
|
|
||||||
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' });
|
||||||
@ -660,7 +664,8 @@ function createFileWatcher() {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
projectGraphClientResponse.hash !==
|
projectGraphClientResponse.hash !==
|
||||||
currentProjectGraphClientResponse.hash
|
currentProjectGraphClientResponse.hash &&
|
||||||
|
sourceMapResponse
|
||||||
) {
|
) {
|
||||||
output.note({ title: 'Graph changes updated.' });
|
output.note({ title: 'Graph changes updated.' });
|
||||||
|
|
||||||
@ -695,7 +700,7 @@ async function createProjectGraphAndSourceMapClientResponse(
|
|||||||
const dependencies = graph.dependencies;
|
const dependencies = graph.dependencies;
|
||||||
|
|
||||||
const hasher = createHash('sha256');
|
const hasher = createHash('sha256');
|
||||||
hasher.update(JSON.stringify({ layout, projects, dependencies }));
|
hasher.update(JSON.stringify({ layout, projects, dependencies, sourceMaps }));
|
||||||
|
|
||||||
const hash = hasher.digest('hex');
|
const hash = hasher.digest('hex');
|
||||||
|
|
||||||
|
|||||||
@ -36,6 +36,7 @@
|
|||||||
"@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/project-details": ["graph/project-details/src/index.ts"],
|
||||||
|
"@nx/graph/shared": ["graph/shared/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"],
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user