chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"@nx/react/babel",
|
||||
{
|
||||
"runtime": "automatic"
|
||||
}
|
||||
]
|
||||
],
|
||||
"plugins": []
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
# This file is used by:
|
||||
# 1. autoprefixer to adjust CSS to support the below specified browsers
|
||||
# 2. babel preset-env to adjust included polyfills
|
||||
#
|
||||
# For additional information regarding the format and rule options, please see:
|
||||
# https://github.com/browserslist/browserslist#queries
|
||||
#
|
||||
# If you need to support different browsers in production, you may tweak the list below.
|
||||
|
||||
last 1 Chrome version
|
||||
last 1 Firefox version
|
||||
last 2 Edge major versions
|
||||
last 2 Safari major version
|
||||
last 2 iOS major versions
|
||||
Firefox ESR
|
||||
not IE 9-11 # For IE 9-11 support, remove 'not'.
|
||||
@@ -0,0 +1,28 @@
|
||||
import { baseConfig, reactHooksV7Off } from '../../eslint.config.mjs';
|
||||
import nx from '@nx/eslint-plugin';
|
||||
|
||||
export default [
|
||||
...baseConfig,
|
||||
...nx.configs['flat/react'],
|
||||
...reactHooksV7Off,
|
||||
{
|
||||
rules: {
|
||||
'@typescript-eslint/no-implicit-any': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
||||
rules: {
|
||||
'no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
paths: ['rxjs'],
|
||||
patterns: ['rxjs/*'],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
ignores: ['src/assets/generated-*'],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,19 @@
|
||||
// nx-ignore-next-line
|
||||
const nxPreset = require('@nx/jest/preset').default;
|
||||
|
||||
module.exports = {
|
||||
...nxPreset,
|
||||
displayName: 'graph-client',
|
||||
transform: {
|
||||
'^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nx/react/plugins/jest',
|
||||
'^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nx/next/babel'] }],
|
||||
},
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
|
||||
coverageDirectory: '../../coverage/graph/client',
|
||||
// The mock for widnow.matchMedia has to be in a separete file and imported before the components to test
|
||||
// for more info check : // https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom
|
||||
modulePathIgnorePatterns: [
|
||||
...(nxPreset.modulePathIgnorePatterns ?? []),
|
||||
'/graph/client/src/app/machines/match-media-mock.spec.ts',
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "@nx/graph-client",
|
||||
"version": "0.0.1",
|
||||
"type": "commonjs",
|
||||
"private": true,
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"devDependencies": {
|
||||
"jest": "catalog:jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nx/graph-internal-ui-render-config": "workspace:",
|
||||
"@nx/graph-internal-project-details": "workspace:",
|
||||
"@nx/graph-internal-ui-project-details": "workspace:",
|
||||
"@nx/graph-migrate": "workspace:",
|
||||
"@nx/graph-shared": "workspace:",
|
||||
"@nx/graph-ui-common": "workspace:",
|
||||
"@nx/graph-ui-code-block": "workspace:"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
plugins: [require('@tailwindcss/postcss'), require('autoprefixer')],
|
||||
};
|
||||
@@ -0,0 +1,99 @@
|
||||
{
|
||||
"name": "graph-client",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "graph/client/src",
|
||||
"projectType": "application",
|
||||
"targets": {
|
||||
"lint": {
|
||||
"options": {
|
||||
"max-warnings": 15
|
||||
}
|
||||
},
|
||||
"generate-dev-environment-js": {
|
||||
"executor": "nx:run-commands",
|
||||
"options": {
|
||||
"commands": ["tsx ./scripts/generate-graph-environment.ts"]
|
||||
}
|
||||
},
|
||||
"generate-graph-base": {
|
||||
"executor": "nx:run-commands",
|
||||
"options": {
|
||||
"commands": ["tsx ./scripts/generate-graph.ts"]
|
||||
}
|
||||
},
|
||||
"generate-graph": {
|
||||
"executor": "nx:run-commands",
|
||||
"options": {
|
||||
"commands": [
|
||||
"npx nx run graph-client:generate-graph-base",
|
||||
"npx nx run graph-client:generate-dev-environment-js"
|
||||
],
|
||||
"parallel": false
|
||||
}
|
||||
},
|
||||
"build-client": {
|
||||
"dependsOn": ["nx:build-native"],
|
||||
"configurations": {
|
||||
"dev": {},
|
||||
"nx-console": {},
|
||||
"release": {},
|
||||
"watch": {},
|
||||
"release-static": {},
|
||||
"dev-e2e": {}
|
||||
},
|
||||
"defaultConfiguration": "release"
|
||||
},
|
||||
"typecheck": {
|
||||
"dependsOn": ["^typecheck", "nx:build-native"],
|
||||
"inputs": [
|
||||
"...",
|
||||
"{workspaceRoot}/packages/devkit/tsconfig.lib.json",
|
||||
"{workspaceRoot}/packages/nx/tsconfig.lib.json"
|
||||
]
|
||||
},
|
||||
"serve-base": {
|
||||
"configurations": {
|
||||
"dev": {},
|
||||
"nx-console": {},
|
||||
"release": {},
|
||||
"watch": {},
|
||||
"release-static": {},
|
||||
"dev-e2e": {}
|
||||
},
|
||||
"defaultConfiguration": "dev"
|
||||
},
|
||||
"serve": {
|
||||
"executor": "nx:run-commands",
|
||||
"configurations": {
|
||||
"dev": {
|
||||
"commands": [
|
||||
"npx nx run graph-client:generate-dev-environment-js",
|
||||
"npx nx serve-base graph-client"
|
||||
],
|
||||
"parallel": false
|
||||
},
|
||||
"dev-e2e": {
|
||||
"commands": ["npx nx serve-base graph-client --configuration dev-e2e"]
|
||||
},
|
||||
"release": {
|
||||
"commands": ["npx nx serve-base graph-client --configuration release"]
|
||||
},
|
||||
"release-static": {
|
||||
"commands": [
|
||||
"npx nx serve-base graph-client --configuration release-static"
|
||||
]
|
||||
},
|
||||
"watch": {
|
||||
"commands": ["npx nx serve-base graph-client --configuration watch"]
|
||||
},
|
||||
"nx-console": {
|
||||
"commands": [
|
||||
"npx nx serve-base graph-client --configuration nx-console"
|
||||
]
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "dev"
|
||||
}
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { RouterProvider } from 'react-router-dom';
|
||||
import { getRouter } from './get-router';
|
||||
import {
|
||||
ThemeProvider,
|
||||
RankDirProvider,
|
||||
} from '@nx/graph-internal-ui-render-config';
|
||||
|
||||
export function App() {
|
||||
return (
|
||||
<ThemeProvider>
|
||||
<RankDirProvider>
|
||||
<RouterProvider router={getRouter()} />
|
||||
</RankDirProvider>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
// nx-ignore-next-line
|
||||
import type { MigrationDetailsWithId } from 'nx/src/config/misc-interfaces';
|
||||
// nx-ignore-next-line
|
||||
import { type FileChange } from 'nx/src/devkit-exports';
|
||||
import { MigrateUI } from '@nx/graph-migrate';
|
||||
|
||||
import { getExternalApiService } from '@nx/graph-shared';
|
||||
import { useSelector } from '@xstate/react';
|
||||
import { Interpreter } from 'xstate';
|
||||
import { MigrateEvents, MigrateState } from './migrate.machine';
|
||||
|
||||
export function MigrateApp({
|
||||
service,
|
||||
}: {
|
||||
service: Interpreter<MigrateState, any, MigrateEvents>;
|
||||
}) {
|
||||
const externalApiService = getExternalApiService();
|
||||
|
||||
const onRunMigration = (
|
||||
migration: MigrationDetailsWithId,
|
||||
configuration: {
|
||||
createCommits: boolean;
|
||||
}
|
||||
) => {
|
||||
externalApiService.postEvent({
|
||||
type: 'run-migration',
|
||||
payload: {
|
||||
migration,
|
||||
configuration,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onRunMany = (
|
||||
migrations: MigrationDetailsWithId[],
|
||||
configuration: {
|
||||
createCommits: boolean;
|
||||
}
|
||||
) => {
|
||||
externalApiService.postEvent({
|
||||
type: 'run-many',
|
||||
payload: {
|
||||
migrations,
|
||||
configuration,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
externalApiService.postEvent({
|
||||
type: 'cancel',
|
||||
});
|
||||
};
|
||||
|
||||
const onFinish = (squashCommits: boolean) => {
|
||||
externalApiService.postEvent({
|
||||
type: 'finish',
|
||||
payload: {
|
||||
squashCommits,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const migrations = useSelector(service, (state) => state.context.migrations);
|
||||
const nxConsoleMetadata = useSelector(
|
||||
service,
|
||||
(state) => state.context.nxConsoleMetadata
|
||||
);
|
||||
const onFileClick = (migration: MigrationDetailsWithId, file: FileChange) => {
|
||||
externalApiService.postEvent({
|
||||
type: 'file-click',
|
||||
payload: {
|
||||
path: file.path,
|
||||
migration: migration,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onSkipMigration = (migration: MigrationDetailsWithId) => {
|
||||
externalApiService.postEvent({
|
||||
type: 'skip-migration',
|
||||
payload: { migration },
|
||||
});
|
||||
};
|
||||
|
||||
const onUndoMigration = (migration: MigrationDetailsWithId) => {
|
||||
externalApiService.postEvent({
|
||||
type: 'undo-migration',
|
||||
payload: { migration },
|
||||
});
|
||||
};
|
||||
|
||||
const onViewImplementation = (migration: MigrationDetailsWithId) => {
|
||||
externalApiService.postEvent({
|
||||
type: 'view-implementation',
|
||||
payload: { migration },
|
||||
});
|
||||
};
|
||||
|
||||
const onViewDocumentation = (migration: MigrationDetailsWithId) => {
|
||||
externalApiService.postEvent({
|
||||
type: 'view-documentation',
|
||||
payload: { migration },
|
||||
});
|
||||
};
|
||||
|
||||
const onStopMigration = (migration: MigrationDetailsWithId) => {
|
||||
externalApiService.postEvent({
|
||||
type: 'stop-migration',
|
||||
payload: { migration },
|
||||
});
|
||||
};
|
||||
|
||||
const onAcknowledgePrompt = (migration: MigrationDetailsWithId) => {
|
||||
externalApiService.postEvent({
|
||||
type: 'acknowledge-prompt',
|
||||
payload: { migration },
|
||||
});
|
||||
};
|
||||
|
||||
const onViewPrompt = (migration: MigrationDetailsWithId) => {
|
||||
externalApiService.postEvent({
|
||||
type: 'view-prompt',
|
||||
payload: { migration },
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<MigrateUI
|
||||
migrations={migrations}
|
||||
nxConsoleMetadata={nxConsoleMetadata}
|
||||
onRunMigration={onRunMigration}
|
||||
onRunMany={onRunMany}
|
||||
onCancel={onCancel}
|
||||
onFinish={onFinish}
|
||||
onFileClick={onFileClick}
|
||||
onSkipMigration={onSkipMigration}
|
||||
onUndoMigration={onUndoMigration}
|
||||
onAcknowledgePrompt={onAcknowledgePrompt}
|
||||
onViewImplementation={onViewImplementation}
|
||||
onViewDocumentation={onViewDocumentation}
|
||||
onViewPrompt={onViewPrompt}
|
||||
onStopMigration={onStopMigration}
|
||||
></MigrateUI>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import { assign } from '@xstate/immer';
|
||||
// nx-ignore-next-line
|
||||
import type {
|
||||
GeneratedMigrationDetails,
|
||||
MigrationDetailsWithId,
|
||||
} from 'nx/src/config/misc-interfaces';
|
||||
// nx-ignore-next-line
|
||||
import { MigrationsJsonMetadata } from 'nx/src/command-line/migrate/migrate-ui-api';
|
||||
|
||||
import { createMachine } from 'xstate';
|
||||
export interface MigrateState {
|
||||
migrations: MigrationDetailsWithId[];
|
||||
nxConsoleMetadata: MigrationsJsonMetadata;
|
||||
}
|
||||
|
||||
export type MigrateEvents = {
|
||||
type: 'loadData';
|
||||
migrations: GeneratedMigrationDetails[];
|
||||
'nx-console': MigrationsJsonMetadata;
|
||||
};
|
||||
|
||||
const initialContext: MigrateState = {
|
||||
migrations: [],
|
||||
nxConsoleMetadata: {},
|
||||
};
|
||||
|
||||
export const migrateMachine = createMachine<MigrateState, MigrateEvents>({
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QFsCWUBOBDALmAxADYD2WEAIrlgNoAMAuoqAA7Gyo6rEB2TIAHogAsAJgA0IAJ6IAjAGYAnADoAHDJFCAbAFYVtGTNpHtAXzMTuxCHD5pMuMH1btOPPoIQBaTROlfNSkZBwSEA7OYgdth4SqgQhI5IIM4cXLxJHpoySiIqcira2gqitEJBPlKIakpyIiIKMrql6kIqKhFRDkokZJBObKluGbKaynU6oXKKIjKaU+KVCLMiSqG02uvF2jqaeiJmZkA */
|
||||
predictableActionArguments: true,
|
||||
preserveActionOrder: true,
|
||||
id: 'migrate',
|
||||
initial: 'idle',
|
||||
context: initialContext,
|
||||
states: {
|
||||
idle: {},
|
||||
loaded: {},
|
||||
},
|
||||
on: {
|
||||
loadData: [
|
||||
{
|
||||
target: 'loaded',
|
||||
actions: [
|
||||
assign((ctx, event) => {
|
||||
ctx.migrations = event.migrations.map((migration, index) => {
|
||||
const duplicateCount = event.migrations
|
||||
.slice(0, index)
|
||||
.filter((m) => m.name === migration.name).length;
|
||||
return {
|
||||
...migration,
|
||||
id:
|
||||
duplicateCount === 0
|
||||
? migration.name
|
||||
: `${migration.name}-${duplicateCount}`,
|
||||
};
|
||||
});
|
||||
ctx.nxConsoleMetadata = event['nx-console'];
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,107 @@
|
||||
import { ProjectDetails } from '@nx/graph-internal-ui-project-details';
|
||||
import { getExternalApiService } from '@nx/graph-shared';
|
||||
import { ErrorToastUI } from '@nx/graph-ui-common';
|
||||
import { ExpandedTargetsProvider } from '@nx/graph-internal-ui-project-details';
|
||||
import { useSelector } from '@xstate/react';
|
||||
import { useCallback } from 'react';
|
||||
import { Interpreter } from 'xstate';
|
||||
import {
|
||||
ProjectDetailsEvents,
|
||||
ProjectDetailsState,
|
||||
} from './project-details.machine';
|
||||
import { GraphStateSerializer } from '@nx/graph';
|
||||
import { ProjectElement } from '@nx/graph/projects';
|
||||
|
||||
export function ProjectDetailsApp({
|
||||
service,
|
||||
}: {
|
||||
service: Interpreter<ProjectDetailsState, any, ProjectDetailsEvents>;
|
||||
}) {
|
||||
const externalApiService = getExternalApiService();
|
||||
|
||||
const project = useSelector(service, (state) => state.context.project);
|
||||
const sourceMap = useSelector(service, (state) => state.context.sourceMap);
|
||||
const errors = useSelector(service, (state) => state.context.errors);
|
||||
const connectedToCloud = useSelector(
|
||||
service,
|
||||
(state) => state.context.connectedToCloud
|
||||
);
|
||||
const disabledTaskSyncGenerators = useSelector(
|
||||
service,
|
||||
(state) => state.context.disabledTaskSyncGenerators
|
||||
);
|
||||
|
||||
const handleViewInProjectGraph = useCallback(
|
||||
(data: { projectName: string }) => {
|
||||
const serializedState = GraphStateSerializer.serialize({
|
||||
c: {},
|
||||
s: {
|
||||
type: 'focused',
|
||||
nodeId: ProjectElement.makeId('project', data.projectName),
|
||||
},
|
||||
});
|
||||
externalApiService.postEvent({
|
||||
type: 'open-project-graph',
|
||||
payload: {
|
||||
projectName: data.projectName,
|
||||
serializedProjectGraphState: serializedState,
|
||||
},
|
||||
});
|
||||
},
|
||||
[externalApiService]
|
||||
);
|
||||
|
||||
const handleViewInTaskGraph = useCallback(
|
||||
(data: { projectName: string; targetName: string }) => {
|
||||
externalApiService.postEvent({
|
||||
type: 'open-task-graph',
|
||||
payload: {
|
||||
projectName: data.projectName,
|
||||
targetName: data.targetName,
|
||||
},
|
||||
});
|
||||
},
|
||||
[externalApiService]
|
||||
);
|
||||
|
||||
const handleRunTarget = useCallback(
|
||||
(data: { projectName: string; targetName: string }) => {
|
||||
externalApiService.postEvent({
|
||||
type: 'run-task',
|
||||
payload: { taskId: `${data.projectName}:${data.targetName}` },
|
||||
});
|
||||
},
|
||||
[externalApiService]
|
||||
);
|
||||
|
||||
const handleNxConnect = useCallback(
|
||||
() =>
|
||||
externalApiService.postEvent({
|
||||
type: 'nx-connect',
|
||||
}),
|
||||
[externalApiService]
|
||||
);
|
||||
|
||||
if (project && sourceMap) {
|
||||
return (
|
||||
<>
|
||||
<ExpandedTargetsProvider>
|
||||
<ProjectDetails
|
||||
project={project as any}
|
||||
sourceMap={sourceMap}
|
||||
onViewInProjectGraph={handleViewInProjectGraph}
|
||||
onViewInTaskGraph={handleViewInTaskGraph}
|
||||
onRunTarget={handleRunTarget}
|
||||
viewInProjectGraphPosition="bottom"
|
||||
connectedToCloud={connectedToCloud}
|
||||
onNxConnect={handleNxConnect}
|
||||
disabledTaskSyncGenerators={disabledTaskSyncGenerators}
|
||||
/>
|
||||
</ExpandedTargetsProvider>
|
||||
<ErrorToastUI errors={errors} />
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { interpret } from 'xstate';
|
||||
import { projectDetailsMachine } from './project-details.machine';
|
||||
|
||||
describe('graphMachine', () => {
|
||||
let service;
|
||||
|
||||
beforeEach(() => {
|
||||
service = interpret(projectDetailsMachine).start();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
service.stop();
|
||||
});
|
||||
|
||||
it('should have initial idle state', () => {
|
||||
expect(service.state.value).toEqual('idle');
|
||||
expect(service.state.context.project).toEqual(null);
|
||||
expect(service.state.context.errors).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should handle setting data', () => {
|
||||
service.send({
|
||||
type: 'loadData',
|
||||
project: {
|
||||
type: 'app',
|
||||
name: 'proj',
|
||||
data: {},
|
||||
},
|
||||
sourceMap: {
|
||||
root: ['project.json', 'nx-core-build-project-json-nodes'],
|
||||
},
|
||||
errors: [{ name: 'ERROR' }],
|
||||
connectedToCloud: true,
|
||||
});
|
||||
|
||||
expect(service.state.value).toEqual('loaded');
|
||||
|
||||
expect(service.state.context.project).toEqual({
|
||||
type: 'app',
|
||||
name: 'proj',
|
||||
data: {},
|
||||
});
|
||||
expect(service.state.context.sourceMap).toEqual({
|
||||
root: ['project.json', 'nx-core-build-project-json-nodes'],
|
||||
});
|
||||
expect(service.state.context.errors).toEqual([{ name: 'ERROR' }]);
|
||||
expect(service.state.context.connectedToCloud).toEqual(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
// nx-ignore-next-line
|
||||
import type { ProjectGraphProjectNode } from '@nx/devkit';
|
||||
// nx-ignore-next-line
|
||||
import { GraphError } from 'nx/src/command-line/graph/graph';
|
||||
|
||||
import { createMachine } from 'xstate';
|
||||
import { assign } from '@xstate/immer';
|
||||
|
||||
export interface ProjectDetailsState {
|
||||
project: null | ProjectGraphProjectNode;
|
||||
sourceMap: null | Record<string, string[]>;
|
||||
errors?: GraphError[];
|
||||
connectedToCloud?: boolean;
|
||||
disabledTaskSyncGenerators?: string[];
|
||||
}
|
||||
|
||||
export type ProjectDetailsEvents = {
|
||||
type: 'loadData';
|
||||
project: ProjectGraphProjectNode;
|
||||
sourceMap: Record<string, string[]>;
|
||||
connectedToCloud: boolean;
|
||||
errors?: GraphError[];
|
||||
disabledTaskSyncGenerators?: string[];
|
||||
};
|
||||
|
||||
const initialContext: ProjectDetailsState = {
|
||||
project: null,
|
||||
sourceMap: null,
|
||||
};
|
||||
|
||||
export const projectDetailsMachine = createMachine<
|
||||
ProjectDetailsState,
|
||||
ProjectDetailsEvents
|
||||
>({
|
||||
predictableActionArguments: true,
|
||||
preserveActionOrder: true,
|
||||
id: 'project-view',
|
||||
initial: 'idle',
|
||||
context: initialContext,
|
||||
states: {
|
||||
idle: {},
|
||||
loaded: {},
|
||||
},
|
||||
on: {
|
||||
loadData: [
|
||||
{
|
||||
target: 'loaded',
|
||||
actions: [
|
||||
assign((ctx, event) => {
|
||||
ctx.project = event.project;
|
||||
ctx.sourceMap = event.sourceMap;
|
||||
ctx.connectedToCloud = event.connectedToCloud;
|
||||
ctx.errors = event.errors;
|
||||
ctx.disabledTaskSyncGenerators = event.disabledTaskSyncGenerators;
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,117 @@
|
||||
import { ExternalApi, getExternalApiService } from '@nx/graph-shared';
|
||||
import { getRouter } from './get-router';
|
||||
import { GraphStateSerializer } from '@nx/graph';
|
||||
import { ProjectElement } from '@nx/graph/projects';
|
||||
|
||||
export class ExternalApiImpl extends ExternalApi {
|
||||
router = getRouter();
|
||||
externalApiService = getExternalApiService();
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.externalApiService.subscribe(
|
||||
({ type, payload }: { type: string; payload: any }) => {
|
||||
if (!this.graphInteractionEventListener) {
|
||||
console.log('graphInteractionEventListener not registered.');
|
||||
return;
|
||||
}
|
||||
this.graphInteractionEventListener({
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
async openProjectDetails(projectName: string, targetName?: string) {
|
||||
await this.router.navigate(
|
||||
`/project-details/${encodeURIComponent(projectName)}`
|
||||
);
|
||||
if (targetName) {
|
||||
this.focusTargetInProjectDetails(targetName);
|
||||
}
|
||||
}
|
||||
|
||||
focusTargetInProjectDetails(targetName: string) {
|
||||
const currentLocation = this.router.state.location;
|
||||
|
||||
const searchParams = new URLSearchParams(currentLocation.search);
|
||||
searchParams.set('expanded', targetName);
|
||||
|
||||
const newUrl = `${currentLocation.pathname}?${searchParams.toString()}`;
|
||||
this.router.navigate(newUrl);
|
||||
}
|
||||
|
||||
focusProject(projectName: string) {
|
||||
const serializedState = GraphStateSerializer.serialize({
|
||||
c: {},
|
||||
s: {
|
||||
type: 'focused',
|
||||
nodeId: ProjectElement.makeId('project', projectName),
|
||||
},
|
||||
});
|
||||
const searchParams = new URLSearchParams();
|
||||
searchParams.set('graph', serializedState);
|
||||
this.router.navigate(`/projects?${searchParams.toString()}`);
|
||||
}
|
||||
|
||||
selectAllProjects() {
|
||||
const serializedState = GraphStateSerializer.serialize({
|
||||
c: { showMode: 'all' },
|
||||
});
|
||||
const searchParams = new URLSearchParams();
|
||||
searchParams.set('graph', serializedState);
|
||||
this.router.navigate(`/projects?${searchParams.toString()}`);
|
||||
}
|
||||
|
||||
showAffectedProjects() {
|
||||
const serializedState = GraphStateSerializer.serialize({
|
||||
c: { showMode: 'affected' },
|
||||
});
|
||||
const searchParams = new URLSearchParams();
|
||||
searchParams.set('graph', serializedState);
|
||||
this.router.navigate(`/projects?${searchParams.toString()}`);
|
||||
}
|
||||
|
||||
focusTarget(projectName: string, targetName: string) {
|
||||
this.router.navigate(
|
||||
`/tasks?targets=${encodeURIComponent(
|
||||
targetName
|
||||
)}&projects=${encodeURIComponent(projectName)}`
|
||||
);
|
||||
}
|
||||
|
||||
selectAllTargetsByName(targetName: string) {
|
||||
this.router.navigate(
|
||||
`/tasks/all?targets=${encodeURIComponent(targetName)}`
|
||||
);
|
||||
}
|
||||
|
||||
enableExperimentalFeatures() {
|
||||
localStorage.setItem('showExperimentalFeatures', 'true');
|
||||
window.appConfig.showExperimentalFeatures = true;
|
||||
}
|
||||
|
||||
disableExperimentalFeatures() {
|
||||
localStorage.setItem('showExperimentalFeatures', 'false');
|
||||
window.appConfig.showExperimentalFeatures = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,572 @@
|
||||
// nx-ignore-next-line
|
||||
import {
|
||||
GraphError,
|
||||
ProjectGraphClientResponse,
|
||||
} from 'nx/src/command-line/graph/graph';
|
||||
|
||||
import {
|
||||
fetchProjectGraph,
|
||||
getExternalApiService,
|
||||
getProjectGraphDataService,
|
||||
useEnvironmentConfig,
|
||||
usePoll,
|
||||
useRouteConstructor,
|
||||
} from '@nx/graph-shared';
|
||||
import { useRankDir, useTheme } from '@nx/graph-internal-ui-render-config';
|
||||
import {
|
||||
Link,
|
||||
useNavigate,
|
||||
useParams,
|
||||
useRouteLoaderData,
|
||||
useSearchParams,
|
||||
} from 'react-router-dom';
|
||||
import { useEffect, useLayoutEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
NxGraphProjectGraphProvider,
|
||||
ProjectGraphHandleEventResult,
|
||||
useProjectGraphContext,
|
||||
} from '@nx/graph/projects';
|
||||
import {
|
||||
NxGraphEmpty,
|
||||
NxGraphDebuggerPanel,
|
||||
useElementPanel,
|
||||
NxGraphToolbar,
|
||||
NxGraphToolbarItemGroup,
|
||||
NxGraphThemeTool,
|
||||
NxGraphRankDirTool,
|
||||
NxGraphResetLayoutTool,
|
||||
NxGraphResetGraphTool,
|
||||
NxGraphShareTool,
|
||||
NxGraphProjectModeTool,
|
||||
NxGraphShowModeTool,
|
||||
NxGraphGroupByFolderTool,
|
||||
NxGraphCollapseEdgesTool,
|
||||
NxGraphElementPanel,
|
||||
NxGraphProjectNodePanelContent,
|
||||
NxGraphProjectNodePanelHeader,
|
||||
NxGraphPanel,
|
||||
NxGraphPanelItemGroup,
|
||||
NxGraphDependencyDistanceControl,
|
||||
NxGraphProjectListControl,
|
||||
NxGraphCompositeProjectNodePanelHeader,
|
||||
NxGraphProjectEdgePanelHeader,
|
||||
NxGraphCompositeProjectEdgePanelHeader,
|
||||
NxGraphCompositeProjectNodePanelContent,
|
||||
NxGraphCompositeProjectEdgePanelContent,
|
||||
NxGraphProjectEdgePanelContent,
|
||||
} from '@nx/graph/ui';
|
||||
import { ElementData, useRendererEvents } from '@nx/graph';
|
||||
import { ArrowLeftCircleIcon } from '@heroicons/react/24/outline';
|
||||
import { ErrorToast } from '@nx/graph-ui-common';
|
||||
import classNames from 'classnames';
|
||||
import { Tab, TabGroup, TabList } from '@headlessui/react';
|
||||
import { useCurrentPath } from '../hooks/use-current-path';
|
||||
|
||||
export function ProjectsShell() {
|
||||
const environmentConfig = useEnvironmentConfig();
|
||||
|
||||
const { errors: routerErrors } = useRouteLoaderData(
|
||||
'selectedWorkspace'
|
||||
) as ProjectGraphClientResponse;
|
||||
|
||||
const [errors, setErrors] = useState<GraphError[] | undefined>(routerErrors);
|
||||
|
||||
const renderPlatform = useMemo(
|
||||
() =>
|
||||
environmentConfig.environment === 'nx-console' ? 'nx-console' : 'nx',
|
||||
[environmentConfig.environment]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setErrors(routerErrors);
|
||||
}, [routerErrors]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<NxGraphProjectGraphProvider renderPlatform={renderPlatform}>
|
||||
<ProjectsShellInner />
|
||||
</NxGraphProjectGraphProvider>
|
||||
<ErrorToast errors={errors} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function ProjectsShellInner() {
|
||||
const projectGraphDataService = getProjectGraphDataService();
|
||||
const externalApiService = getExternalApiService();
|
||||
const graphContext = useProjectGraphContext();
|
||||
const {
|
||||
containerRef,
|
||||
send,
|
||||
orchestrator,
|
||||
sendRendererConfigEvent,
|
||||
restoreGraphState,
|
||||
serializedGraphState,
|
||||
handleEventResult,
|
||||
} = graphContext;
|
||||
|
||||
const navigate = useNavigate();
|
||||
const routeConstructor = useRouteConstructor();
|
||||
const { currentPath } = useCurrentPath();
|
||||
const { resolvedTheme, theme, setTheme } = useTheme();
|
||||
const { rankDir, setRankDir } = useRankDir();
|
||||
const environmentConfig = useEnvironmentConfig();
|
||||
const params = useParams();
|
||||
const selectedWorkspaceLoaderData = useRouteLoaderData(
|
||||
'selectedWorkspace'
|
||||
) as ProjectGraphClientResponse;
|
||||
const [lastHash, setLastHash] = useState(selectedWorkspaceLoaderData.hash);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const graphState = searchParams.get('graph');
|
||||
|
||||
useRendererEvents(orchestrator, {
|
||||
compositeProjectNodeDoubleTap: ({ data }) => {
|
||||
if (data.expanded) {
|
||||
send({ type: 'collapseCompositeNode', compositeNodeId: data.id });
|
||||
} else {
|
||||
send({
|
||||
type: 'selectNode',
|
||||
nodeId: data.id,
|
||||
pendingAction: { type: 'expand' },
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const [element] = useElementPanel<
|
||||
| ElementData.ProjectNode
|
||||
| ElementData.CompositeProjectNode
|
||||
| ElementData.SelectedCompositeProjectEdge
|
||||
| ElementData.SelectedProjectEdge
|
||||
>(orchestrator);
|
||||
|
||||
useEffect(() => {
|
||||
if (!orchestrator) return;
|
||||
|
||||
send(
|
||||
{
|
||||
type: 'initGraph',
|
||||
projects: selectedWorkspaceLoaderData.projects,
|
||||
dependencies: selectedWorkspaceLoaderData.dependencies,
|
||||
fileMap: selectedWorkspaceLoaderData.fileMap,
|
||||
affectedProjects: selectedWorkspaceLoaderData.affected,
|
||||
},
|
||||
{
|
||||
type: 'updateRendererConfig',
|
||||
updater: () => ({
|
||||
autoExpand: 15,
|
||||
rankDir,
|
||||
theme: resolvedTheme,
|
||||
showMode: 'all',
|
||||
}),
|
||||
},
|
||||
{ type: 'showAll' }
|
||||
);
|
||||
}, [orchestrator]);
|
||||
|
||||
useEffect(() => {
|
||||
sendRendererConfigEvent({ type: 'themeChange', theme: resolvedTheme });
|
||||
}, [resolvedTheme]);
|
||||
|
||||
useEffect(() => {
|
||||
sendRendererConfigEvent({ type: 'rankDirChange', rankDir });
|
||||
}, [rankDir]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!graphState || !orchestrator || graphState === serializedGraphState) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = restoreGraphState(graphState);
|
||||
if (!result) return;
|
||||
|
||||
if (result.rendererConfig.rankDir !== rankDir) {
|
||||
setRankDir(result.rendererConfig.rankDir);
|
||||
}
|
||||
|
||||
if (
|
||||
result.rendererConfig.platform !== 'nx-console' &&
|
||||
result.rendererConfig.theme !== resolvedTheme
|
||||
) {
|
||||
setTheme(result.rendererConfig.theme);
|
||||
}
|
||||
}, [graphState, orchestrator]);
|
||||
|
||||
useEffect(() => {
|
||||
setSearchParams(
|
||||
(params) => {
|
||||
const currentGraphParams = params.get('graph');
|
||||
|
||||
// this means serialization went wrong
|
||||
if (serializedGraphState === null && currentGraphParams) {
|
||||
params.delete('graph');
|
||||
return params;
|
||||
}
|
||||
|
||||
if (
|
||||
serializedGraphState &&
|
||||
currentGraphParams !== serializedGraphState
|
||||
) {
|
||||
params.set('graph', serializedGraphState);
|
||||
}
|
||||
return params;
|
||||
},
|
||||
{ preventScrollReset: true }
|
||||
);
|
||||
}, [serializedGraphState]);
|
||||
|
||||
usePoll(
|
||||
async () => {
|
||||
const response: ProjectGraphClientResponse = await fetchProjectGraph(
|
||||
projectGraphDataService,
|
||||
params,
|
||||
environmentConfig.appConfig
|
||||
);
|
||||
|
||||
if (response.hash === lastHash) {
|
||||
return;
|
||||
}
|
||||
|
||||
send({
|
||||
type: 'updateGraph',
|
||||
projects: response.projects,
|
||||
dependencies: response.dependencies,
|
||||
fileMap: response.fileMap,
|
||||
affectedProjects: response.affected,
|
||||
});
|
||||
|
||||
setLastHash(response.hash);
|
||||
},
|
||||
5000,
|
||||
environmentConfig.watch
|
||||
);
|
||||
|
||||
const onViewProjectDetailsClick = (project: ElementData.ProjectNode) => {
|
||||
if (handleEventResult.rendererConfig.platform === 'nx-dev') return;
|
||||
|
||||
if (handleEventResult.rendererConfig.platform === 'nx-console') {
|
||||
return externalApiService.postEvent({
|
||||
type: 'open-project-config',
|
||||
payload: { projectName: project.name, projectId: project.id },
|
||||
});
|
||||
}
|
||||
|
||||
return navigate(
|
||||
routeConstructor(
|
||||
{ pathname: `/project-details/${encodeURIComponent(project.name)}` },
|
||||
() => {
|
||||
const searchParams = new URLSearchParams();
|
||||
searchParams.set('projectId', project.id);
|
||||
return searchParams;
|
||||
}
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col">
|
||||
{environmentConfig.appConfig.showDebugger ? (
|
||||
<NxGraphDebuggerPanel
|
||||
position="top"
|
||||
containerClassName="static bg-white text-slate-700 dark:bg-slate-900 dark:text-slate-300 border-slate-300 dark:border-slate-800"
|
||||
expandButtonClassName="text-slate-600 dark:text-slate-200 hover:text-slate-900 dark:hover:text-slate-100 bg-slate-50 dark:bg-slate-800 border-slate-300 dark:border-slate-600"
|
||||
copyButtonClassName="text-slate-600 dark:text-slate-200 hover:text-slate-900 dark:hover:text-slate-100 bg-slate-50 dark:bg-slate-800 border-slate-300 dark:border-slate-600"
|
||||
textareaClassName="bg-slate-50 dark:bg-slate-800"
|
||||
statsClassName="text-slate-600 dark:text-slate-200"
|
||||
textareaLabelClassName="text-slate-600 dark:text-slate-200"
|
||||
/>
|
||||
) : null}
|
||||
<div className="relative h-full w-full bg-white dark:bg-slate-900">
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="h-full w-full bg-white dark:bg-slate-900"
|
||||
id="cytoscape-graph"
|
||||
></div>
|
||||
|
||||
<NxGraphToolbar toolbarClassName="border-slate-300 dark:border-slate-700 divide-slate-300 dark:divide-slate-700 z-50 divide-x">
|
||||
<NxGraphToolbarItemGroup className="pr-0">
|
||||
{handleEventResult.rendererConfig.platform !== 'nx-console' ? (
|
||||
<NxGraphThemeTool
|
||||
theme={theme}
|
||||
setTheme={setTheme}
|
||||
toolPopoverButtonClassName="text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-slate-100 hover:bg-slate-50 dark:hover:bg-slate-800 border-slate-300 dark:border-slate-600"
|
||||
toolPopoverPanelClassName="bg-white dark:bg-slate-900 border-slate-200 dark:border-slate-700 z-50"
|
||||
toolPopoverPanelItemClassName="hover:bg-sky-500 dark:hover:bg-sky-600 hover:text-slate-200 dark:text-slate-300"
|
||||
toolPopoverPanelItemActiveClassName="text-slate-200 bg-sky-500 dark:bg-sky-600"
|
||||
/>
|
||||
) : null}
|
||||
<NxGraphRankDirTool
|
||||
onRankDirChange={setRankDir}
|
||||
toolPopoverButtonClassName="text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-slate-100 hover:bg-slate-50 dark:hover:bg-slate-800 border-slate-300 dark:border-slate-600"
|
||||
toolPopoverPanelClassName="bg-white dark:bg-slate-900 border-slate-200 dark:border-slate-700 z-50"
|
||||
toolPopoverPanelItemClassName="hover:bg-sky-500 dark:hover:bg-sky-600 hover:text-slate-200 dark:text-slate-300"
|
||||
toolPopoverPanelItemActiveClassName="text-slate-200 bg-sky-500 dark:bg-sky-600"
|
||||
/>
|
||||
</NxGraphToolbarItemGroup>
|
||||
<NxGraphToolbarItemGroup className="pl-2 pr-0">
|
||||
<NxGraphResetLayoutTool toolClassName="text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-slate-100 hover:bg-slate-50 dark:hover:bg-slate-800 border-slate-300 dark:border-slate-600" />
|
||||
<NxGraphResetGraphTool toolClassName="text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-slate-100 hover:bg-slate-50 dark:hover:bg-slate-800 border-slate-300 dark:border-slate-600" />
|
||||
<NxGraphShareTool
|
||||
toolPopoverButtonClassName="text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-slate-100 hover:bg-slate-50 dark:hover:bg-slate-800 border-slate-300 dark:border-slate-600"
|
||||
toolPopoverPanelClassName="bg-white dark:bg-slate-900 border-slate-200 dark:border-slate-700 z-50"
|
||||
toolPopoverPanelItemClassName="hover:bg-sky-500 dark:hover:bg-sky-600 hover:text-slate-200 dark:text-slate-300"
|
||||
/>
|
||||
</NxGraphToolbarItemGroup>
|
||||
<NxGraphToolbarItemGroup className="pl-2 pr-0">
|
||||
<NxGraphProjectModeTool
|
||||
toolPopoverButtonClassName="text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-slate-100 hover:bg-slate-50 dark:hover:bg-slate-800 border-slate-300 dark:border-slate-600"
|
||||
toolPopoverPanelClassName="bg-white dark:bg-slate-900 border-slate-200 dark:border-slate-700 z-50"
|
||||
toolPopoverPanelItemClassName="hover:bg-sky-500 dark:hover:bg-sky-600 hover:text-slate-200 dark:text-slate-300"
|
||||
toolPopoverPanelItemActiveClassName="text-slate-200 bg-sky-500 dark:bg-sky-600"
|
||||
/>
|
||||
{handleEventResult.rendererConfig.mode === 'individual' && (
|
||||
<>
|
||||
<NxGraphGroupByFolderTool
|
||||
toolClassName="text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-slate-100 hover:bg-slate-50 dark:hover:bg-slate-800 border-slate-300 dark:border-slate-600"
|
||||
toolActiveClassName="bg-sky-500 dark:bg-sky-600 text-white dark:text-white"
|
||||
/>
|
||||
<NxGraphCollapseEdgesTool
|
||||
toolClassName="text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-slate-100 hover:bg-slate-50 dark:hover:bg-slate-800 border-slate-300 dark:border-slate-600"
|
||||
toolActiveClassName="bg-sky-500 dark:bg-sky-600 text-white dark:text-white"
|
||||
toolDisabledClassName="opacity-50 cursor-not-allowed hover:bg-transparent hover:text-slate-400 dark:hover:text-slate-600 border-slate-300 dark:border-slate-600"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</NxGraphToolbarItemGroup>
|
||||
<NxGraphToolbarItemGroup last className="pl-2">
|
||||
<NxGraphShowModeTool
|
||||
toolPopoverButtonClassName="text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-slate-100 hover:bg-slate-50 dark:hover:bg-slate-800 border-slate-300 dark:border-slate-600"
|
||||
toolPopoverPanelClassName="bg-white dark:bg-slate-900 border-slate-200 dark:border-slate-700 z-50"
|
||||
toolPopoverPanelItemClassName="hover:bg-sky-500 dark:hover:bg-sky-600 hover:text-slate-200 dark:text-slate-300"
|
||||
toolPopoverPanelItemActiveClassName="text-slate-200 bg-sky-500 dark:bg-sky-600"
|
||||
/>
|
||||
</NxGraphToolbarItemGroup>
|
||||
</NxGraphToolbar>
|
||||
|
||||
<NxGraphEmpty className="z-40 flex items-center gap-2 bg-white text-slate-700 dark:bg-slate-900 dark:text-slate-300">
|
||||
<ArrowLeftCircleIcon className="h-6 w-6 text-slate-600 dark:text-slate-400" />
|
||||
<h4 className="text-slate-700 dark:text-slate-300">
|
||||
No projects found for the current graph configuration.
|
||||
</h4>
|
||||
</NxGraphEmpty>
|
||||
|
||||
{handleEventResult.rendererConfig.platform === 'nx-console' ? (
|
||||
<ProjectGraphControlsPanel handleEventResult={handleEventResult} />
|
||||
) : (
|
||||
<div className="absolute bottom-0 left-0 top-4 flex flex-col">
|
||||
<div className="mb-2 ml-4 min-w-96 max-w-96">
|
||||
<TabGroup>
|
||||
<TabList className="flex rounded-lg bg-slate-100 p-1 dark:bg-slate-800">
|
||||
<Tab
|
||||
className={classNames(
|
||||
'text-md flex-1 rounded-md px-3 py-1.5 font-medium transition-colors',
|
||||
currentPath === '/projects'
|
||||
? 'bg-white text-slate-900 shadow-sm dark:bg-slate-700 dark:text-slate-100'
|
||||
: 'text-slate-600 hover:bg-slate-50 hover:text-slate-900 dark:text-slate-400 dark:hover:bg-slate-700 dark:hover:text-slate-100'
|
||||
)}
|
||||
>
|
||||
Projects
|
||||
</Tab>
|
||||
<Tab
|
||||
className={classNames(
|
||||
'text-md flex-1 rounded-md px-3 py-1.5 font-medium transition-colors',
|
||||
currentPath === '/tasks'
|
||||
? 'bg-white text-slate-900 shadow-sm dark:bg-slate-700 dark:text-slate-100'
|
||||
: 'text-slate-600 hover:bg-slate-50 hover:text-slate-900 dark:text-slate-400 dark:hover:bg-slate-700 dark:hover:text-slate-100'
|
||||
)}
|
||||
onClick={() => navigate('../tasks')}
|
||||
>
|
||||
Tasks
|
||||
</Tab>
|
||||
</TabList>
|
||||
</TabGroup>
|
||||
</div>
|
||||
<div className="relative flex-1">
|
||||
<ProjectGraphControlsPanel
|
||||
handleEventResult={handleEventResult}
|
||||
closable={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<NxGraphElementPanel
|
||||
element={element}
|
||||
panelContainerClassName="border-slate-300 dark:border-slate-700 bg-white dark:bg-slate-900"
|
||||
panelHeaderClassName="border-slate-300 dark:border-slate-700"
|
||||
panelContentContainerClassName="divide-slate-300 dark:divide-slate-700"
|
||||
header={{
|
||||
project: (element, { open, close }) => (
|
||||
<NxGraphProjectNodePanelHeader
|
||||
element={element}
|
||||
open={open}
|
||||
close={close}
|
||||
badgeClassName="bg-slate-100 dark:bg-slate-700 text-slate-700 dark:text-slate-300 border-slate-300 dark:border-slate-600 uppercase"
|
||||
elementNameClassName="text-slate-900 dark:text-slate-100"
|
||||
closeButtonClassName="hover:bg-slate-100 dark:hover:bg-slate-700"
|
||||
/>
|
||||
),
|
||||
'composite-project': (element, { open, close }) => (
|
||||
<NxGraphCompositeProjectNodePanelHeader
|
||||
element={element}
|
||||
open={open}
|
||||
close={close}
|
||||
badgeClassName="bg-slate-100 dark:bg-slate-700 text-slate-700 dark:text-slate-300 border-slate-300 dark:border-slate-600 uppercase"
|
||||
elementNameClassName="text-slate-900 dark:text-slate-100"
|
||||
closeButtonClassName="hover:bg-slate-100 dark:hover:bg-slate-700"
|
||||
/>
|
||||
),
|
||||
'project-edge': (element, { open, close }) => (
|
||||
<NxGraphProjectEdgePanelHeader
|
||||
element={element}
|
||||
open={open}
|
||||
close={close}
|
||||
badgeClassName="bg-slate-100 dark:bg-slate-700 text-slate-700 dark:text-slate-300 border-slate-300 dark:border-slate-600 uppercase"
|
||||
closeButtonClassName="hover:bg-slate-100 dark:hover:bg-slate-700"
|
||||
/>
|
||||
),
|
||||
'composite-project-edge': (element, { open, close }) => (
|
||||
<NxGraphCompositeProjectEdgePanelHeader
|
||||
element={element}
|
||||
open={open}
|
||||
close={close}
|
||||
badgeClassName="bg-slate-100 dark:bg-slate-700 text-slate-700 dark:text-slate-300 border-slate-300 dark:border-slate-600 uppercase"
|
||||
closeButtonClassName="hover:bg-slate-100 dark:hover:bg-slate-700"
|
||||
/>
|
||||
),
|
||||
}}
|
||||
>
|
||||
{{
|
||||
project: (element) => (
|
||||
<NxGraphProjectNodePanelContent
|
||||
element={element}
|
||||
sectionHeadingClassName="text-slate-900 dark:text-slate-100"
|
||||
sectionTextClassName="text-slate-700 dark:text-slate-300"
|
||||
tagBadgeClassName="bg-slate-100 dark:bg-slate-700 text-slate-700 dark:text-slate-300 border-slate-300 dark:border-slate-600"
|
||||
actionButtonClassName="bg-slate-100/60 dark:bg-slate-700/60 text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700 hover:text-slate-900 dark:hover:text-slate-100 border-slate-300 dark:border-slate-600"
|
||||
cancelActionButtonClassName="bg-red-500 dark:bg-red-600 text-white hover:bg-red-600 dark:hover:bg-red-700 border-red-500 dark:border-red-600"
|
||||
dependencyItemClassName="text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700"
|
||||
dependentItemClassName="text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700"
|
||||
emptyItemListClassName="text-slate-600 dark:text-slate-400"
|
||||
traceAlgorithmButtonClassName="bg-slate-100/60 dark:bg-slate-700/60 text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700 hover:text-slate-900 dark:hover:text-slate-100 border-slate-300 dark:border-slate-600"
|
||||
traceAlgorithmActiveButtonClassName="bg-sky-500 dark:bg-sky-600 text-white hover:bg-sky-600 dark:hover:bg-sky-700 border-sky-500 dark:border-sky-600"
|
||||
traceableProjectItemClassName="text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700 justify-between"
|
||||
traceableProjectSelectedItemClassName="bg-sky-500/10 dark:bg-sky-600/10 text-slate-900 dark:text-slate-100"
|
||||
traceEndFilterInputClassName="bg-white dark:bg-slate-800 border-slate-300 dark:border-slate-600 text-slate-900 dark:text-slate-100 placeholder-slate-400 dark:placeholder-slate-500 focus:border-sky-500 dark:focus:border-sky-400"
|
||||
onViewProjectDetailsClick={() =>
|
||||
onViewProjectDetailsClick(element)
|
||||
}
|
||||
/>
|
||||
),
|
||||
'project-edge': (element) => (
|
||||
<NxGraphProjectEdgePanelContent
|
||||
element={element}
|
||||
sectionHeadingClassName="text-slate-900 dark:text-slate-100"
|
||||
sectionTextClassName="text-slate-700 dark:text-slate-300"
|
||||
projectNameClassName="text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700"
|
||||
sectionListContainerClassName="border-slate-200 dark:border-slate-700"
|
||||
sectionListSectionClassName="bg-slate-50 dark:bg-slate-800"
|
||||
sectionListHeaderClassName="text-slate-700 dark:text-slate-300 bg-slate-200 dark:bg-slate-800"
|
||||
sectionListHeaderLabelClassName="text-slate-600 dark:text-slate-400"
|
||||
sectionListItemsClassName="divide-slate-200 dark:divide-slate-700"
|
||||
sectionListItemClassName="bg-slate-50 dark:bg-slate-700"
|
||||
sectionListItemLabelClassName="text-slate-900 dark:text-slate-100"
|
||||
/>
|
||||
),
|
||||
'composite-project': (element) => (
|
||||
<NxGraphCompositeProjectNodePanelContent
|
||||
element={element}
|
||||
sectionHeadingClassName="text-slate-900 dark:text-slate-100"
|
||||
sectionTextClassName="text-slate-700 dark:text-slate-300"
|
||||
actionButtonClassName="bg-slate-100/60 dark:bg-slate-700/60 text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700 hover:text-slate-900 dark:hover:text-slate-100 border-slate-300 dark:border-slate-600"
|
||||
cancelActionButtonClassName="bg-red-500 dark:bg-red-600 text-white hover:bg-red-600 dark:hover:bg-red-700 border-red-500 dark:border-red-600"
|
||||
confirmActionButtonClassName="bg-sky-500 dark:bg-sky-600 text-white hover:bg-sky-600 dark:hover:bg-sky-700 border-sky-500 dark:border-sky-600"
|
||||
multiselectContainerClassName="text-slate-700 dark:text-slate-300"
|
||||
multiselectFilterInputClassName="bg-white dark:bg-slate-800 border-slate-300 dark:border-slate-600 text-slate-900 dark:text-slate-100 focus:border-sky-500 dark:focus:border-sky-400"
|
||||
multiselectEmptyStateClassName="text-slate-600 dark:text-slate-400"
|
||||
multiselectListItemClassName="hover:bg-slate-100 dark:hover:bg-slate-700"
|
||||
multiselectCheckboxClassName="border-slate-300 dark:border-slate-600 accent-sky-500"
|
||||
multiselectLabelClassName="text-slate-700 dark:text-slate-300"
|
||||
multiselectSectionHeaderClassName="text-slate-900 dark:text-slate-100 border-slate-300 dark:border-slate-700"
|
||||
/>
|
||||
),
|
||||
'composite-project-edge': (element) => (
|
||||
<NxGraphCompositeProjectEdgePanelContent
|
||||
element={element}
|
||||
sectionHeadingClassName="text-slate-900 dark:text-slate-100"
|
||||
sectionTextClassName="text-slate-700 dark:text-slate-300"
|
||||
projectNameClassName="text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700"
|
||||
sectionListContainerClassName="border-slate-200 dark:border-slate-700"
|
||||
sectionListSectionClassName="bg-slate-50 dark:bg-slate-800"
|
||||
sectionListHeaderClassName="text-slate-700 dark:text-slate-300 bg-slate-200 dark:bg-slate-800"
|
||||
sectionListHeaderLabelClassName="text-slate-600 dark:text-slate-400"
|
||||
sectionListItemsClassName="divide-slate-200 dark:divide-slate-700"
|
||||
sectionListItemClassName="bg-slate-50 dark:bg-slate-700"
|
||||
sectionListItemLabelClassName="text-slate-900 dark:text-slate-100"
|
||||
/>
|
||||
),
|
||||
}}
|
||||
</NxGraphElementPanel>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ProjectGraphControlsPanel({
|
||||
handleEventResult,
|
||||
closable = true,
|
||||
}: {
|
||||
handleEventResult: ProjectGraphHandleEventResult;
|
||||
closable?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<NxGraphPanel
|
||||
anchor="left"
|
||||
closable={closable}
|
||||
initialOpen
|
||||
panelContainerClassName="border-slate-300 dark:border-slate-700 bg-white dark:bg-slate-900"
|
||||
panelHeaderClassName="border-slate-300 dark:border-slate-700"
|
||||
panelCloseButtonClassName="hover:bg-slate-100 dark:hover:bg-slate-700"
|
||||
panelContentContainerClassName="divide-slate-300 dark:divide-slate-700"
|
||||
panelTriggerButtonClassName="border-slate-300 dark:border-slate-700 bg-white/80 dark:bg-slate-900/80 text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700"
|
||||
>
|
||||
{handleEventResult.state.type === 'focused' ? (
|
||||
<NxGraphPanelItemGroup>
|
||||
<NxGraphDependencyDistanceControl
|
||||
controlLabelClassName="text-slate-900 dark:text-slate-100"
|
||||
controlDescriptionClassName="text-slate-600 dark:text-slate-400"
|
||||
controlInputClassName="border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-800 text-slate-900 dark:text-slate-100 focus:border-sky-500 dark:focus:border-sky-400 focus:ring-sky-500/20 dark:focus:ring-sky-400/20"
|
||||
removeFocusButtonClassName="bg-red-500 dark:bg-red-600 text-white"
|
||||
/>
|
||||
</NxGraphPanelItemGroup>
|
||||
) : null}
|
||||
<NxGraphPanelItemGroup last>
|
||||
<NxGraphProjectListControl
|
||||
projectItemClassName={({ rendered }) =>
|
||||
rendered
|
||||
? 'text-slate-900 dark:text-slate-50 hover:bg-slate-100 dark:hover:bg-slate-700'
|
||||
: 'text-slate-600 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700'
|
||||
}
|
||||
projectItemSelectIconClassName={() =>
|
||||
'hover:bg-slate-100 dark:hover:bg-slate-700 hover:text-sky-500 dark:hover:text-sky-400'
|
||||
}
|
||||
searchInputClassName="bg-white dark:bg-slate-800 text-slate-900 dark:text-slate-100 border-slate-300 dark:border-slate-600 focus:border-transparent focus:ring-2 focus:ring-sky-500/20 dark:focus:ring-sky-400/20 focus:outline-none data-[focus]:border-sky-500 dark:data-[focus]:border-sky-400 data-[focus]:ring-2 data-[focus]:ring-sky-500/20 dark:data-[focus]:ring-sky-400/20"
|
||||
searchInputClearButtonClassName="bg-slate-100 dark:bg-slate-700 text-slate-700 dark:text-slate-300 hover:bg-sky-500 dark:hover:bg-sky-600 hover:text-white border-slate-300 dark:border-slate-600"
|
||||
projectListEmptyClassName="text-slate-600 dark:text-slate-400"
|
||||
projectSectionHeaderClassName="text-slate-900 dark:text-slate-100"
|
||||
projectItemCheckboxClassName={({ rendered }) =>
|
||||
classNames(
|
||||
'border-slate-300 dark:border-slate-600',
|
||||
rendered ? 'bg-sky-500' : ''
|
||||
)
|
||||
}
|
||||
projectItemTextClassName={({ rendered }) =>
|
||||
rendered
|
||||
? 'text-slate-900 dark:text-slate-100'
|
||||
: 'text-slate-400 dark:text-slate-400'
|
||||
}
|
||||
/>
|
||||
</NxGraphPanelItemGroup>
|
||||
</NxGraphPanel>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,668 @@
|
||||
// nx-ignore-next-line
|
||||
import type {
|
||||
GraphError,
|
||||
ProjectGraphClientResponse,
|
||||
TaskGraphClientResponse,
|
||||
} from 'nx/src/command-line/graph/graph';
|
||||
import type { ProjectGraphProjectNode } from '@nx/devkit';
|
||||
|
||||
import { useTaskGraphContext, NxGraphTaskGraphProvider } from '@nx/graph/tasks';
|
||||
import {
|
||||
getExternalApiService,
|
||||
getProjectGraphDataService,
|
||||
useEnvironmentConfig,
|
||||
useRouteConstructor,
|
||||
} from '@nx/graph-shared';
|
||||
import { useRankDir, useTheme } from '@nx/graph-internal-ui-render-config';
|
||||
import {
|
||||
useNavigate,
|
||||
useRouteLoaderData,
|
||||
useSearchParams,
|
||||
} from 'react-router-dom';
|
||||
import { useEffect, useLayoutEffect, useMemo, useState } from 'react';
|
||||
import { ErrorToast } from '@nx/graph-ui-common';
|
||||
import {
|
||||
NxGraphDebuggerPanel,
|
||||
NxGraphEmpty,
|
||||
NxGraphProjectListControl,
|
||||
NxGraphCustomProjectItem,
|
||||
NxGraphDropdownMultiselect,
|
||||
NxGraphTaskNodePanelContent,
|
||||
useElementPanel,
|
||||
NxGraphToolbar,
|
||||
NxGraphToolbarItemGroup,
|
||||
NxGraphThemeTool,
|
||||
NxGraphRankDirTool,
|
||||
NxGraphResetLayoutTool,
|
||||
NxGraphResetGraphTool,
|
||||
NxGraphShareTool,
|
||||
NxGraphGroupByProjectTool,
|
||||
NxGraphPanel,
|
||||
NxGraphElementPanel,
|
||||
NxGraphTaskNodePanelHeader,
|
||||
} from '@nx/graph/ui';
|
||||
import {
|
||||
ArrowLeftCircleIcon,
|
||||
DocumentMagnifyingGlassIcon,
|
||||
PlayIcon,
|
||||
} from '@heroicons/react/24/outline';
|
||||
import { useCurrentPath } from '../hooks/use-current-path';
|
||||
import { createTaskName } from '../util';
|
||||
import classNames from 'classnames';
|
||||
import { ElementData, RenderPlatform } from '@nx/graph';
|
||||
import { Tab, TabGroup, TabList } from '@headlessui/react';
|
||||
|
||||
export function TasksShell() {
|
||||
const environmentConfig = useEnvironmentConfig();
|
||||
|
||||
const [errors, setErrors] = useState<GraphError[] | undefined>(undefined);
|
||||
|
||||
const renderPlatform = useMemo(
|
||||
() =>
|
||||
environmentConfig.environment === 'nx-console' ? 'nx-console' : 'nx',
|
||||
[environmentConfig.environment]
|
||||
);
|
||||
|
||||
const { errors: routerErrors } = useRouteLoaderData(
|
||||
'selectedWorkspace'
|
||||
) as ProjectGraphClientResponse;
|
||||
|
||||
const tasksRouteData = useRouteLoaderData('tasks') as TaskGraphClientResponse;
|
||||
const allTasksRouteData = useRouteLoaderData(
|
||||
'tasksAll'
|
||||
) as TaskGraphClientResponse;
|
||||
|
||||
const taskGraphError = useMemo(
|
||||
() => allTasksRouteData?.error || tasksRouteData?.error,
|
||||
[allTasksRouteData, tasksRouteData]
|
||||
);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const allErrors: GraphError[] = [];
|
||||
|
||||
if (routerErrors) {
|
||||
allErrors.push(...routerErrors);
|
||||
}
|
||||
|
||||
if (taskGraphError) {
|
||||
allErrors.push({
|
||||
message: taskGraphError,
|
||||
pluginName: 'graph',
|
||||
cause: 'TaskGraphClientResponse error',
|
||||
name: 'graph',
|
||||
stack: '',
|
||||
});
|
||||
}
|
||||
|
||||
if (allErrors.length === 0) return;
|
||||
|
||||
setErrors(allErrors);
|
||||
}, [routerErrors, taskGraphError]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<NxGraphTaskGraphProvider renderPlatform={renderPlatform}>
|
||||
<TasksShellInner />
|
||||
</NxGraphTaskGraphProvider>
|
||||
<ErrorToast errors={errors} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function TasksShellInner() {
|
||||
const graphContext = useTaskGraphContext();
|
||||
const {
|
||||
containerRef,
|
||||
send,
|
||||
orchestrator,
|
||||
sendRendererConfigEvent,
|
||||
restoreGraphState,
|
||||
serializedGraphState,
|
||||
handleEventResult,
|
||||
} = graphContext;
|
||||
|
||||
const navigate = useNavigate();
|
||||
const { currentPath } = useCurrentPath();
|
||||
const { resolvedTheme, theme, setTheme } = useTheme();
|
||||
const { rankDir, setRankDir } = useRankDir();
|
||||
const environmentConfig = useEnvironmentConfig();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const createRoute = useRouteConstructor();
|
||||
const isAllRoute = useMemo(() => currentPath === '/tasks/all', [currentPath]);
|
||||
|
||||
const selectedWorkspaceLoaderData = useRouteLoaderData(
|
||||
'selectedWorkspace'
|
||||
) as ProjectGraphClientResponse & { targets: string[] };
|
||||
const tasksRouteData = useRouteLoaderData('tasks') as TaskGraphClientResponse;
|
||||
const allTasksRouteData = useRouteLoaderData(
|
||||
'tasksAll'
|
||||
) as TaskGraphClientResponse;
|
||||
|
||||
const tasksData = useMemo(() => {
|
||||
return allTasksRouteData || tasksRouteData;
|
||||
}, [allTasksRouteData, tasksRouteData]);
|
||||
|
||||
const selectedTargets = useMemo(() => {
|
||||
const targetsParam = searchParams.get('targets');
|
||||
return targetsParam ? targetsParam.split(' ').filter(Boolean) : [];
|
||||
}, [searchParams]);
|
||||
|
||||
const allProjectsWithTargetsAndNoErrors = useMemo(
|
||||
() =>
|
||||
selectedWorkspaceLoaderData.projects.filter(
|
||||
(project) =>
|
||||
selectedTargets.length > 0 &&
|
||||
selectedTargets.some((target) =>
|
||||
project.data.targets?.hasOwnProperty(target)
|
||||
) &&
|
||||
!tasksData.error // If there's a global error, exclude all projects
|
||||
),
|
||||
[selectedWorkspaceLoaderData.projects, selectedTargets, tasksData.error]
|
||||
);
|
||||
|
||||
const selectedProjects = useMemo(
|
||||
() =>
|
||||
isAllRoute
|
||||
? allProjectsWithTargetsAndNoErrors
|
||||
: selectedWorkspaceLoaderData.projects.filter((project) =>
|
||||
(searchParams.get('projects') ?? '')
|
||||
.split(' ')
|
||||
.includes(project.name)
|
||||
),
|
||||
[allProjectsWithTargetsAndNoErrors, searchParams, isAllRoute]
|
||||
);
|
||||
|
||||
const selectedProjectNames = useMemo(
|
||||
() => selectedProjects.map((project) => project.name),
|
||||
[selectedProjects]
|
||||
);
|
||||
|
||||
const graphState = searchParams.get('graph');
|
||||
|
||||
const [element] = useElementPanel<
|
||||
ElementData.TaskNode | ElementData.CompositeTaskNode
|
||||
>(orchestrator);
|
||||
|
||||
useEffect(() => {
|
||||
if (!orchestrator) return;
|
||||
|
||||
send({
|
||||
type: 'initGraph',
|
||||
projects: selectedWorkspaceLoaderData.projects,
|
||||
taskGraph: tasksData.taskGraph,
|
||||
});
|
||||
}, [orchestrator]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!orchestrator) return;
|
||||
|
||||
send({
|
||||
type: 'mergeGraph',
|
||||
projects: selectedWorkspaceLoaderData.projects,
|
||||
taskGraph: tasksData.taskGraph,
|
||||
});
|
||||
}, [orchestrator, tasksData.taskGraph]);
|
||||
|
||||
useEffect(() => {
|
||||
sendRendererConfigEvent({ type: 'themeChange', theme: resolvedTheme });
|
||||
}, [resolvedTheme]);
|
||||
|
||||
useEffect(() => {
|
||||
sendRendererConfigEvent({ type: 'rankDirChange', rankDir });
|
||||
}, [rankDir]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!orchestrator) return;
|
||||
|
||||
const taskIds = selectedProjects.flatMap((project) =>
|
||||
selectedTargets.map((target) => {
|
||||
const resolvedConfiguration =
|
||||
project.data.targets?.[target]?.defaultConfiguration;
|
||||
return createTaskName(project.name, target, resolvedConfiguration);
|
||||
})
|
||||
);
|
||||
|
||||
send({ type: 'show', taskIds });
|
||||
}, [selectedProjects, selectedTargets, send, orchestrator]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!graphState || !orchestrator || graphState === serializedGraphState) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = restoreGraphState(graphState);
|
||||
if (!result) return;
|
||||
|
||||
if (result.rendererConfig.rankDir !== rankDir) {
|
||||
setRankDir(result.rendererConfig.rankDir);
|
||||
}
|
||||
|
||||
if (result.rendererConfig.theme !== resolvedTheme) {
|
||||
setTheme(result.rendererConfig.theme);
|
||||
}
|
||||
}, [graphState, orchestrator]);
|
||||
|
||||
useEffect(() => {
|
||||
setSearchParams(
|
||||
(params) => {
|
||||
const currentGraphParams = params.get('graph');
|
||||
|
||||
// this means serialization went wrong
|
||||
if (serializedGraphState === null && currentGraphParams) {
|
||||
params.delete('graph');
|
||||
return params;
|
||||
}
|
||||
|
||||
if (
|
||||
serializedGraphState &&
|
||||
currentGraphParams !== serializedGraphState
|
||||
) {
|
||||
params.set('graph', serializedGraphState);
|
||||
}
|
||||
return params;
|
||||
},
|
||||
{ preventScrollReset: true }
|
||||
);
|
||||
}, [serializedGraphState]);
|
||||
|
||||
const onTargetsSelectionChange = (selectedIds: string[]) => {
|
||||
const newSearchParams = new URLSearchParams(searchParams);
|
||||
// remove selected projects when targets change
|
||||
newSearchParams.delete('projects');
|
||||
|
||||
if (selectedIds.length > 0) {
|
||||
newSearchParams.set('targets', selectedIds.join(' '));
|
||||
} else {
|
||||
newSearchParams.delete('targets');
|
||||
}
|
||||
|
||||
setSearchParams((prev) => {
|
||||
prev.delete('projects');
|
||||
if (selectedIds.length > 0) {
|
||||
prev.set('targets', selectedIds.join(' '));
|
||||
} else {
|
||||
prev.delete('targets');
|
||||
}
|
||||
return prev;
|
||||
});
|
||||
};
|
||||
|
||||
const onToggleProject = (projectName: string, isSelected: boolean) => {
|
||||
if (isSelected) {
|
||||
const newSelectedProjects = selectedProjectNames.filter(
|
||||
(selectedProject) => selectedProject !== projectName
|
||||
);
|
||||
|
||||
const newParams = new URLSearchParams(searchParams);
|
||||
if (newSelectedProjects.length === 0) {
|
||||
newParams.delete('projects');
|
||||
} else {
|
||||
newParams.set('projects', newSelectedProjects.join(' '));
|
||||
}
|
||||
|
||||
navigate(
|
||||
createRoute(
|
||||
{ pathname: '/tasks', search: newParams.toString() },
|
||||
() => newParams
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const newSelectedProjects = [...selectedProjectNames, projectName];
|
||||
const allProjectsSelected =
|
||||
newSelectedProjects.length === allProjectsWithTargetsAndNoErrors.length;
|
||||
|
||||
const newParams = new URLSearchParams(searchParams);
|
||||
if (allProjectsSelected) {
|
||||
newParams.delete('projects');
|
||||
} else {
|
||||
newParams.set('projects', newSelectedProjects.join(' '));
|
||||
}
|
||||
|
||||
navigate(
|
||||
createRoute(
|
||||
{
|
||||
pathname: allProjectsSelected ? '/tasks/all' : '/tasks',
|
||||
search: newParams.toString(),
|
||||
},
|
||||
() => newParams
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col">
|
||||
{environmentConfig.appConfig.showDebugger ? (
|
||||
<NxGraphDebuggerPanel
|
||||
position="top"
|
||||
containerClassName="static bg-white text-slate-700 dark:bg-slate-900 dark:text-slate-300 border-slate-300 dark:border-slate-800"
|
||||
expandButtonClassName="text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-slate-100 bg-slate-50 dark:bg-slate-800 border-slate-300 dark:border-slate-600"
|
||||
copyButtonClassName="text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-slate-100 bg-slate-50 dark:bg-slate-800 border-slate-300 dark:border-slate-600"
|
||||
textareaClassName="bg-slate-50 dark:bg-slate-800"
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<div className="relative h-full w-full bg-white dark:bg-slate-900">
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="h-full w-full bg-white dark:bg-slate-900"
|
||||
id="cytoscape-graph"
|
||||
></div>
|
||||
|
||||
<NxGraphToolbar toolbarClassName="border-slate-300 dark:border-slate-700 divide-slate-300 dark:divide-slate-700 z-50 divide-x">
|
||||
<NxGraphToolbarItemGroup className="pr-0">
|
||||
<NxGraphThemeTool
|
||||
theme={theme}
|
||||
setTheme={setTheme}
|
||||
toolPopoverButtonClassName="text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-slate-100 hover:bg-slate-50 dark:hover:bg-slate-800 border-slate-300 dark:border-slate-600"
|
||||
toolPopoverPanelClassName="bg-white dark:bg-slate-900 border-slate-200 dark:border-slate-700 z-50"
|
||||
toolPopoverPanelItemClassName="hover:bg-sky-500 dark:hover:bg-sky-600 hover:text-slate-200 dark:text-slate-300"
|
||||
toolPopoverPanelItemActiveClassName="text-slate-200 bg-sky-500 dark:bg-sky-600"
|
||||
/>
|
||||
<NxGraphRankDirTool
|
||||
onRankDirChange={setRankDir}
|
||||
toolPopoverButtonClassName="text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-slate-100 hover:bg-slate-50 dark:hover:bg-slate-800 border-slate-300 dark:border-slate-600"
|
||||
toolPopoverPanelClassName="bg-white dark:bg-slate-900 border-slate-200 dark:border-slate-700 z-50"
|
||||
toolPopoverPanelItemClassName="hover:bg-sky-500 dark:hover:bg-sky-600 hover:text-slate-200 dark:text-slate-300"
|
||||
toolPopoverPanelItemActiveClassName="text-slate-200 bg-sky-500 dark:bg-sky-600"
|
||||
/>
|
||||
</NxGraphToolbarItemGroup>
|
||||
<NxGraphToolbarItemGroup className="pl-2 pr-0">
|
||||
<NxGraphGroupByProjectTool
|
||||
toolClassName="text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-slate-100 hover:bg-slate-50 dark:hover:bg-slate-800 border-slate-300 dark:border-slate-600"
|
||||
toolActiveClassName="bg-sky-500 dark:bg-sky-600 text-white dark:text-white"
|
||||
/>
|
||||
</NxGraphToolbarItemGroup>
|
||||
<NxGraphToolbarItemGroup last className="pl-2">
|
||||
<NxGraphResetLayoutTool toolClassName="text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-slate-100 hover:bg-slate-50 dark:hover:bg-slate-800 border-slate-300 dark:border-slate-600" />
|
||||
<NxGraphResetGraphTool toolClassName="text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-slate-100 hover:bg-slate-50 dark:hover:bg-slate-800 border-slate-300 dark:border-slate-600" />
|
||||
<NxGraphShareTool
|
||||
toolPopoverButtonClassName="text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-slate-100 hover:bg-slate-50 dark:hover:bg-slate-800 border-slate-300 dark:border-slate-600"
|
||||
toolPopoverPanelClassName="bg-white dark:bg-slate-900 border-slate-200 dark:border-slate-700 z-50"
|
||||
toolPopoverPanelItemClassName="hover:bg-sky-500 dark:hover:bg-sky-600 hover:text-slate-200 dark:text-slate-300"
|
||||
/>
|
||||
</NxGraphToolbarItemGroup>
|
||||
</NxGraphToolbar>
|
||||
|
||||
<NxGraphEmpty className="z-30 flex items-center gap-2 bg-white text-slate-700 dark:bg-slate-900 dark:text-slate-300">
|
||||
<ArrowLeftCircleIcon className="h-6 w-6 text-slate-600 dark:text-slate-400" />
|
||||
<h4 className="text-slate-700 dark:text-slate-300">
|
||||
No tasks found for the current graph configuration.
|
||||
</h4>
|
||||
</NxGraphEmpty>
|
||||
|
||||
{handleEventResult.rendererConfig.platform === 'nx-console' ? (
|
||||
<TaskGraphControlsPanel
|
||||
targets={selectedWorkspaceLoaderData.targets}
|
||||
selectedTargets={selectedTargets}
|
||||
selectedProjects={selectedProjectNames}
|
||||
allProjects={allProjectsWithTargetsAndNoErrors}
|
||||
error={tasksData.error}
|
||||
onTargetsSelectionChange={onTargetsSelectionChange}
|
||||
onToggleProject={onToggleProject}
|
||||
/>
|
||||
) : (
|
||||
<div className="absolute bottom-0 left-0 top-4 z-50 flex flex-col">
|
||||
<TabGroup className="mb-2 ml-4 min-w-96 max-w-96">
|
||||
<TabList className="flex rounded-lg bg-slate-100 p-1 dark:bg-slate-800">
|
||||
<Tab
|
||||
className={classNames(
|
||||
'text-md flex-1 rounded-md px-3 py-1.5 font-medium transition-colors',
|
||||
currentPath === '/projects'
|
||||
? 'bg-white text-slate-900 shadow-sm dark:bg-slate-700 dark:text-slate-100'
|
||||
: 'text-slate-600 hover:bg-slate-50 hover:text-slate-900 dark:text-slate-400 dark:hover:bg-slate-700 dark:hover:text-slate-100'
|
||||
)}
|
||||
onClick={() => navigate('../projects')}
|
||||
>
|
||||
Projects
|
||||
</Tab>
|
||||
<Tab
|
||||
className={classNames(
|
||||
'text-md flex-1 rounded-md px-3 py-1.5 font-medium transition-colors',
|
||||
currentPath === '/tasks'
|
||||
? 'bg-white text-slate-900 shadow-sm dark:bg-slate-700 dark:text-slate-100'
|
||||
: 'text-slate-600 hover:bg-slate-50 hover:text-slate-900 dark:text-slate-400 dark:hover:bg-slate-700 dark:hover:text-slate-100'
|
||||
)}
|
||||
>
|
||||
Tasks
|
||||
</Tab>
|
||||
</TabList>
|
||||
</TabGroup>
|
||||
<div className="relative flex-1">
|
||||
<TaskGraphControlsPanel
|
||||
closable={false}
|
||||
targets={selectedWorkspaceLoaderData.targets}
|
||||
selectedTargets={selectedTargets}
|
||||
selectedProjects={selectedProjectNames}
|
||||
allProjects={allProjectsWithTargetsAndNoErrors}
|
||||
error={tasksData.error}
|
||||
onTargetsSelectionChange={onTargetsSelectionChange}
|
||||
onToggleProject={onToggleProject}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<NxGraphElementPanel
|
||||
element={element}
|
||||
panelContainerClassName="border-slate-300 dark:border-slate-700 bg-white dark:bg-slate-900"
|
||||
panelHeaderClassName="border-slate-300 dark:border-slate-700"
|
||||
panelContentContainerClassName="divide-slate-300 dark:divide-slate-700"
|
||||
header={{
|
||||
task: (element, { open, close }) => (
|
||||
<NxGraphTaskNodePanelHeader
|
||||
element={element}
|
||||
open={open}
|
||||
close={close}
|
||||
elementNameClassName="text-slate-900 dark:text-slate-100"
|
||||
closeButtonClassName="hover:bg-slate-100 dark:hover:bg-slate-700"
|
||||
taskFlagBadgeClassName="bg-slate-100 dark:bg-slate-700 text-slate-700 dark:text-slate-300 border-slate-300 dark:border-slate-600"
|
||||
/>
|
||||
),
|
||||
}}
|
||||
>
|
||||
{{
|
||||
task: (element) => (
|
||||
<TaskNodeDetails
|
||||
element={element}
|
||||
platform={handleEventResult.rendererConfig.platform}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
</NxGraphElementPanel>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface TaskNodeDetailsProps {
|
||||
platform: RenderPlatform;
|
||||
element: ElementData.TaskNode;
|
||||
}
|
||||
|
||||
function TaskNodeDetails({ element, platform }: TaskNodeDetailsProps) {
|
||||
const projectGraphDataService = getProjectGraphDataService();
|
||||
|
||||
const externalApiService = getExternalApiService();
|
||||
const routeConstructor = useRouteConstructor();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [inputs, setInputs] = useState<Record<string, string[]> | null>(null);
|
||||
const [isLoadingInputs, setIsLoadingInputs] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
// Only fetch if the service has the method
|
||||
if (!projectGraphDataService.getExpandedTaskInputs) return;
|
||||
|
||||
projectGraphDataService
|
||||
.getExpandedTaskInputs(element.name)
|
||||
.then((expandedInputs) => {
|
||||
setInputs(expandedInputs);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to fetch task inputs:', error);
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoadingInputs(false);
|
||||
});
|
||||
}, [element.name, projectGraphDataService]);
|
||||
|
||||
const onViewProjectDetailsClick = () => {
|
||||
if (platform === 'nx-dev') return;
|
||||
const projectName = element.project.name;
|
||||
const targetName = element.task.target.target;
|
||||
|
||||
if (platform === 'nx-console') {
|
||||
return externalApiService.postEvent({
|
||||
type: 'open-project-config',
|
||||
payload: { projectName, targetName },
|
||||
});
|
||||
}
|
||||
|
||||
navigate(
|
||||
routeConstructor(
|
||||
{
|
||||
pathname: `/project-details/${encodeURIComponent(projectName)}`,
|
||||
search: `expanded=${targetName}`,
|
||||
},
|
||||
false
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const onRunTaskClick = () => {
|
||||
if (platform !== 'nx-console') return;
|
||||
|
||||
externalApiService.postEvent({
|
||||
type: 'run-task',
|
||||
payload: { taskId: element.id, takeName: element.name },
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<NxGraphTaskNodePanelContent
|
||||
element={element}
|
||||
inputs={inputs}
|
||||
isLoadingInputs={isLoadingInputs}
|
||||
sectionHeadingClassName="text-slate-900 dark:text-slate-100"
|
||||
sectionTextClassName="text-slate-700 dark:text-slate-300"
|
||||
actionButtonClassName="bg-slate-100/60 dark:bg-slate-700/60 text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700 hover:text-slate-900 dark:hover:text-slate-100 border-slate-300 dark:border-slate-600"
|
||||
sectionListContainerClassName="border-slate-200 dark:border-slate-700"
|
||||
sectionListSectionClassName="bg-slate-50 dark:bg-slate-800"
|
||||
sectionListHeaderClassName="text-slate-700 dark:text-slate-300 bg-slate-200 dark:bg-slate-800"
|
||||
sectionListHeaderLabelClassName="text-slate-600 dark:text-slate-400"
|
||||
sectionListItemsClassName="divide-slate-200 dark:divide-slate-600"
|
||||
sectionListItemClassName="bg-slate-50 dark:bg-slate-700"
|
||||
sectionListItemLabelClassName="text-slate-900 dark:text-slate-100"
|
||||
loadingSkeletonHeaderClassName="bg-slate-200 dark:bg-slate-600"
|
||||
loadingSkeletonItemClassName="bg-slate-100 dark:bg-slate-700"
|
||||
loadingSkeletonListClassName="bg-slate-50 dark:bg-slate-800"
|
||||
>
|
||||
{({ NxGraphActionButton }) => (
|
||||
<>
|
||||
<NxGraphActionButton onClick={onViewProjectDetailsClick}>
|
||||
<DocumentMagnifyingGlassIcon className="h-4 w-4" />
|
||||
<span className="text-sm">View in Project Details</span>
|
||||
</NxGraphActionButton>
|
||||
|
||||
{platform === 'nx-console' ? (
|
||||
<NxGraphActionButton onClick={onRunTaskClick}>
|
||||
<PlayIcon className="h-4 w-4" />
|
||||
<span className="text-sm">Run task</span>
|
||||
</NxGraphActionButton>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</NxGraphTaskNodePanelContent>
|
||||
);
|
||||
}
|
||||
|
||||
interface TaskGraphControlsPanelProps {
|
||||
targets: string[];
|
||||
selectedTargets: string[];
|
||||
selectedProjects: string[];
|
||||
|
||||
allProjects: ProjectGraphProjectNode[];
|
||||
closable?: boolean;
|
||||
error?: string;
|
||||
|
||||
onTargetsSelectionChange: (selectedIds: string[]) => void;
|
||||
onToggleProject: (projectName: string, isSelected: boolean) => void;
|
||||
}
|
||||
|
||||
function TaskGraphControlsPanel({
|
||||
targets,
|
||||
selectedTargets,
|
||||
selectedProjects,
|
||||
closable = true,
|
||||
allProjects,
|
||||
error = '',
|
||||
onTargetsSelectionChange,
|
||||
onToggleProject,
|
||||
}: TaskGraphControlsPanelProps) {
|
||||
return (
|
||||
<NxGraphPanel
|
||||
anchor="left"
|
||||
closable={closable}
|
||||
initialOpen
|
||||
panelContainerClassName="border-slate-300 dark:border-slate-700 bg-white dark:bg-slate-900"
|
||||
panelHeaderClassName="border-slate-300 dark:border-slate-700"
|
||||
panelCloseButtonClassName="hover:bg-slate-100 dark:hover:bg-slate-700"
|
||||
panelContentContainerClassName="divide-slate-300 dark:divide-slate-700"
|
||||
panelTriggerButtonClassName="border-slate-300 dark:border-slate-700 bg-white/80 dark:bg-slate-900/80 text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700"
|
||||
>
|
||||
<NxGraphDropdownMultiselect
|
||||
items={targets.map((target) => ({ id: target, name: target }))}
|
||||
selectedIds={selectedTargets}
|
||||
onSelectionChange={onTargetsSelectionChange}
|
||||
containerClassName="bg-white dark:bg-slate-800"
|
||||
triggerClassName="bg-white dark:bg-slate-700 border-slate-300 dark:border-slate-600 text-slate-900 dark:text-slate-100 hover:bg-slate-50 dark:hover:bg-slate-600"
|
||||
triggerPlaceholderClassName="text-slate-500 dark:text-slate-400"
|
||||
triggerChevronClassName="text-slate-500 dark:text-slate-400"
|
||||
selectedChipClassName="bg-sky-100 dark:bg-sky-800 text-sky-900 dark:text-sky-100 border-sky-200 dark:border-sky-700"
|
||||
selectedCountClassName="text-slate-700 dark:text-slate-300"
|
||||
chipRemoveButtonClassName="text-sky-700 dark:text-sky-300 hover:text-sky-900 dark:hover:text-sky-100"
|
||||
dropdownClassName="bg-white dark:bg-slate-800 border-slate-300 dark:border-slate-600"
|
||||
dropdownContentClassName="bg-white dark:bg-slate-800"
|
||||
multiselectContainerClassName="bg-white dark:bg-slate-800 border-slate-300 dark:border-slate-600"
|
||||
multiselectFilterInputClassName="bg-white dark:bg-slate-700 border-slate-300 dark:border-slate-600 text-slate-900 dark:text-slate-100 placeholder:text-slate-500 dark:placeholder:text-slate-400"
|
||||
multiselectEmptyStateClassName="text-slate-500 dark:text-slate-400"
|
||||
multiselectListClassName="bg-white dark:bg-slate-800"
|
||||
multiselectListItemClassName="hover:bg-slate-50 dark:hover:bg-slate-700"
|
||||
multiselectCheckboxClassName="text-sky-600 dark:text-sky-400"
|
||||
multiselectLabelClassName="text-slate-900 dark:text-slate-100"
|
||||
/>
|
||||
<NxGraphProjectListControl
|
||||
projects={allProjects.map((p) => ({
|
||||
type: p.type,
|
||||
id: p.data.name,
|
||||
name: p.data.name,
|
||||
selected: selectedProjects.includes(p.name),
|
||||
rendered: false,
|
||||
affected: false,
|
||||
}))}
|
||||
projectListEmptyClassName="text-slate-500 dark:text-slate-400"
|
||||
projectSectionHeaderClassName="text-slate-700 dark:text-slate-300"
|
||||
searchInputClassName="bg-white dark:bg-slate-700 border-slate-300 dark:border-slate-600 text-slate-900 dark:text-slate-100 placeholder:text-slate-500 dark:placeholder:text-slate-400"
|
||||
>
|
||||
{(props) => (
|
||||
<NxGraphCustomProjectItem
|
||||
project={props.project}
|
||||
error={error}
|
||||
onToggle={() => {
|
||||
onToggleProject(props.project.name, props.project.selected);
|
||||
}}
|
||||
projectItemClassName={({ selected }) =>
|
||||
classNames(
|
||||
'hover:bg-slate-100 dark:hover:bg-slate-700',
|
||||
selected ? 'opacity-100' : 'opacity-70'
|
||||
)
|
||||
}
|
||||
projectNameClassName="text-slate-900 dark:text-slate-100 dark:hover:bg-slate-700"
|
||||
/>
|
||||
)}
|
||||
</NxGraphProjectListControl>
|
||||
</NxGraphPanel>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { useRouteError } from 'react-router-dom';
|
||||
|
||||
export function TasksSidebarErrorBoundary() {
|
||||
let error = useRouteError();
|
||||
console.error(error);
|
||||
return (
|
||||
<div className="mt-8 px-4">
|
||||
<h2 className="mt-8 border-b border-solid border-slate-200/10 text-lg font-light text-slate-400 dark:text-slate-500">
|
||||
Error
|
||||
</h2>
|
||||
<p>There was a problem loading your task graph.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { createBrowserRouter, createHashRouter } from 'react-router-dom';
|
||||
import { getRoutesForEnvironment } from './routes';
|
||||
import { getEnvironmentConfig } from '@nx/graph-shared';
|
||||
|
||||
let router;
|
||||
|
||||
export function getRouter() {
|
||||
if (!router) {
|
||||
const environmentConfig = getEnvironmentConfig();
|
||||
let routerCreate = createBrowserRouter;
|
||||
if (
|
||||
environmentConfig.localMode === 'build' ||
|
||||
environmentConfig.environment === 'nx-console'
|
||||
) {
|
||||
routerCreate = createHashRouter;
|
||||
}
|
||||
|
||||
router = routerCreate(getRoutesForEnvironment());
|
||||
}
|
||||
|
||||
return router;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { matchRoutes, useLocation } from 'react-router-dom';
|
||||
import { getRoutesForEnvironment } from '../routes';
|
||||
import { useState } from 'react';
|
||||
import { getEnvironmentConfig } from '@nx/graph-shared';
|
||||
|
||||
export const useCurrentPath = () => {
|
||||
const [lastLocation, setLastLocation] = useState<string>();
|
||||
const [lastPath, setLastPath] = useState<{
|
||||
workspace: string;
|
||||
currentPath: string;
|
||||
}>();
|
||||
|
||||
const location = useLocation();
|
||||
|
||||
if (location.pathname === lastLocation) {
|
||||
return lastPath;
|
||||
}
|
||||
|
||||
setLastLocation(location.pathname);
|
||||
|
||||
const route = matchRoutes(getRoutesForEnvironment(), location).at(-1);
|
||||
|
||||
const { environment } = getEnvironmentConfig();
|
||||
|
||||
let currentPath: { workspace: string; currentPath: string };
|
||||
// if using dev routes, remove first segment for workspace
|
||||
if (environment === 'dev') {
|
||||
currentPath = {
|
||||
workspace: route.pathname.split('/')[1],
|
||||
currentPath: `/${route.pathname.split('/').slice(2).join('/')}`,
|
||||
};
|
||||
} else {
|
||||
currentPath = { workspace: 'local', currentPath: route.pathname };
|
||||
}
|
||||
setLastPath(currentPath);
|
||||
return currentPath;
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
// nx-ignore-next-line
|
||||
import type {
|
||||
ProjectGraphClientResponse,
|
||||
TaskGraphClientResponse,
|
||||
} from 'nx/src/command-line/graph/graph';
|
||||
|
||||
export interface ProjectGraphService {
|
||||
getHash: () => Promise<string>;
|
||||
getProjectGraph: (url: string) => Promise<ProjectGraphClientResponse>;
|
||||
getTaskGraph: (url: string) => Promise<TaskGraphClientResponse>;
|
||||
setTaskInputsUrl?: (url: string) => void;
|
||||
getExpandedTaskInputs?: (taskId: string) => Promise<Record<string, string[]>>;
|
||||
getSourceMaps?: (
|
||||
url: string
|
||||
) => Promise<Record<string, Record<string, string[]>>>;
|
||||
}
|
||||
|
||||
export interface Environment {
|
||||
environment: 'dev' | 'watch' | 'release';
|
||||
}
|
||||
@@ -0,0 +1,349 @@
|
||||
import { redirect, RouteObject, json, LoaderFunction } from 'react-router-dom';
|
||||
|
||||
// nx-ignore-next-line
|
||||
import type {
|
||||
GraphError,
|
||||
ProjectGraphClientResponse,
|
||||
} from 'nx/src/command-line/graph/graph';
|
||||
// nx-ignore-next-line
|
||||
import type { ProjectGraphProjectNode } from 'nx/src/config/project-graph';
|
||||
import {
|
||||
getEnvironmentConfig,
|
||||
getProjectGraphDataService,
|
||||
} from '@nx/graph-shared';
|
||||
import { TasksSidebarErrorBoundary } from './feature-tasks/tasks-sidebar-error-boundary';
|
||||
import { ProjectDetailsPage } from '@nx/graph-internal-project-details';
|
||||
import { ErrorBoundary } from './ui-components/error-boundary';
|
||||
import { taskGraphClientCache } from './task-graph-client-cache';
|
||||
import { ProjectsShell } from './feature-projects/projects-shell';
|
||||
import { TasksShell } from './feature-tasks/tasks-shell';
|
||||
import { GraphSerializableState, GraphStateSerializer } from '@nx/graph';
|
||||
|
||||
const { appConfig } = getEnvironmentConfig();
|
||||
const projectGraphDataService = getProjectGraphDataService();
|
||||
|
||||
export function getRoutesForEnvironment() {
|
||||
if (getEnvironmentConfig().environment === 'dev') {
|
||||
return devRoutes;
|
||||
} else {
|
||||
return releaseRoutes;
|
||||
}
|
||||
}
|
||||
|
||||
const workspaceDataLoader = async (
|
||||
selectedWorkspaceId: string,
|
||||
requestFull = false
|
||||
) => {
|
||||
const workspaceInfo = appConfig.workspaces.find(
|
||||
(graph) => graph.id === selectedWorkspaceId
|
||||
);
|
||||
|
||||
if (!workspaceInfo) {
|
||||
throw new Error(`Workspace ${selectedWorkspaceId} not found`);
|
||||
}
|
||||
|
||||
projectGraphDataService.setTaskInputsUrl?.(workspaceInfo.taskInputsUrl);
|
||||
|
||||
const projectGraph: ProjectGraphClientResponse =
|
||||
await projectGraphDataService.getProjectGraph(
|
||||
workspaceInfo.projectGraphUrl,
|
||||
requestFull
|
||||
);
|
||||
|
||||
const targetsSet = new Set<string>();
|
||||
|
||||
projectGraph.projects.forEach((project) => {
|
||||
Object.keys(project.data.targets ?? {}).forEach((targetName) => {
|
||||
targetsSet.add(targetName);
|
||||
});
|
||||
});
|
||||
|
||||
const targets = Array.from(targetsSet).sort((a, b) => a.localeCompare(b));
|
||||
|
||||
const sourceMaps = await sourceMapsLoader(selectedWorkspaceId);
|
||||
|
||||
return { ...projectGraph, targets, sourceMaps };
|
||||
};
|
||||
|
||||
const sourceMapsLoader = async (selectedWorkspaceId: string) => {
|
||||
const workspaceInfo = appConfig.workspaces.find(
|
||||
(graph) => graph.id === selectedWorkspaceId
|
||||
);
|
||||
|
||||
if (!workspaceInfo) {
|
||||
throw new Error(`Workspace ${selectedWorkspaceId} not found`);
|
||||
}
|
||||
|
||||
return await projectGraphDataService.getSourceMaps(
|
||||
workspaceInfo.sourceMapsUrl
|
||||
);
|
||||
};
|
||||
|
||||
const tasksLoader: LoaderFunction = async ({ params, request }) => {
|
||||
const selectedWorkspaceId =
|
||||
params.selectedWorkspaceId ?? appConfig.defaultWorkspaceId;
|
||||
|
||||
const workspaceInfo = appConfig.workspaces.find(
|
||||
(graph) => graph.id === selectedWorkspaceId
|
||||
);
|
||||
|
||||
if (!workspaceInfo) {
|
||||
throw new Error(`Workspace ${selectedWorkspaceId} not found`);
|
||||
}
|
||||
|
||||
// bust the task graph cache if workspaceId changes
|
||||
taskGraphClientCache.setWorkspace(selectedWorkspaceId);
|
||||
|
||||
const requestUrl = new URL(request.url);
|
||||
const targetsParam = requestUrl.searchParams.get('targets');
|
||||
const projectsParam = requestUrl.searchParams.get('projects');
|
||||
|
||||
const selectedTargets = targetsParam
|
||||
? targetsParam.split(' ').filter(Boolean)
|
||||
: [];
|
||||
const requestedProjects = projectsParam
|
||||
? projectsParam.split(' ').filter(Boolean)
|
||||
: undefined;
|
||||
|
||||
// if no targets are selected, empty taskGraph response
|
||||
if (selectedTargets.length === 0) {
|
||||
return {
|
||||
taskGraph: {
|
||||
tasks: {},
|
||||
dependencies: {},
|
||||
continuousDependencies: {},
|
||||
roots: [],
|
||||
},
|
||||
error: null,
|
||||
};
|
||||
}
|
||||
|
||||
const isAllRoute = requestUrl.pathname.endsWith('/all');
|
||||
|
||||
// if we don't request any projects and we're not on the "all" route,
|
||||
// we return empty taskGraph response. consumers need to select projects
|
||||
if (!requestedProjects && !isAllRoute) {
|
||||
return {
|
||||
taskGraph: {
|
||||
tasks: {},
|
||||
dependencies: {},
|
||||
continuousDependencies: {},
|
||||
roots: [],
|
||||
},
|
||||
error: null,
|
||||
};
|
||||
}
|
||||
|
||||
// Check if we have cached data for this exact combination
|
||||
const { cached, missingTargets, missingProjects } =
|
||||
taskGraphClientCache.getCached(selectedTargets, requestedProjects);
|
||||
|
||||
if (cached) return cached;
|
||||
|
||||
const response = await projectGraphDataService.getSpecificTaskGraph(
|
||||
workspaceInfo.taskGraphUrl,
|
||||
missingProjects,
|
||||
missingTargets
|
||||
);
|
||||
|
||||
if (response.error) return response;
|
||||
|
||||
// only merge if we don't have error
|
||||
return taskGraphClientCache.mergeTaskGraph(
|
||||
response,
|
||||
missingTargets,
|
||||
missingProjects
|
||||
);
|
||||
};
|
||||
|
||||
const projectDetailsLoader = async (
|
||||
selectedWorkspaceId: string,
|
||||
projectName: string
|
||||
): Promise<{
|
||||
hash: string;
|
||||
project: ProjectGraphProjectNode;
|
||||
sourceMap: Record<string, string[]>;
|
||||
errors?: GraphError[];
|
||||
connectedToCloud?: boolean;
|
||||
disabledTaskSyncGenerators?: string[];
|
||||
}> => {
|
||||
const workspaceData = await workspaceDataLoader(selectedWorkspaceId);
|
||||
const sourceMaps = await sourceMapsLoader(selectedWorkspaceId);
|
||||
|
||||
const project = workspaceData.projects.find(
|
||||
(project) => project.name === projectName
|
||||
);
|
||||
if (!project) {
|
||||
throw json({
|
||||
id: 'project-not-found',
|
||||
projectName,
|
||||
errors: workspaceData.errors,
|
||||
});
|
||||
}
|
||||
return {
|
||||
hash: workspaceData.hash,
|
||||
project,
|
||||
sourceMap: sourceMaps[project.data.root],
|
||||
errors: workspaceData.errors,
|
||||
connectedToCloud: workspaceData.connectedToCloud,
|
||||
disabledTaskSyncGenerators: workspaceData.disabledTaskSyncGenerators,
|
||||
};
|
||||
};
|
||||
|
||||
const tasksRouteObject: RouteObject = {
|
||||
element: <TasksShell />,
|
||||
errorElement: <TasksSidebarErrorBoundary />,
|
||||
loader: tasksLoader,
|
||||
shouldRevalidate: ({ currentParams, nextParams, currentUrl, nextUrl }) => {
|
||||
// Always revalidate if workspace changes
|
||||
if (
|
||||
!currentParams.selectedWorkspaceId ||
|
||||
currentParams.selectedWorkspaceId !== nextParams.selectedWorkspaceId
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Revalidate if query parameters change (targets or projects)
|
||||
const currentSearchParams = new URLSearchParams(currentUrl.search);
|
||||
const nextSearchParams = new URLSearchParams(nextUrl.search);
|
||||
|
||||
const currentTargets = currentSearchParams.get('targets');
|
||||
const nextTargets = nextSearchParams.get('targets');
|
||||
const currentProjects = currentSearchParams.get('projects');
|
||||
const nextProjects = nextSearchParams.get('projects');
|
||||
|
||||
return currentTargets !== nextTargets || currentProjects !== nextProjects;
|
||||
},
|
||||
};
|
||||
|
||||
const childRoutes: RouteObject[] = [
|
||||
{
|
||||
path: 'projects',
|
||||
element: <ProjectsShell />,
|
||||
loader: ({ request }) => {
|
||||
const url = new URL(request.url);
|
||||
if (!url.searchParams.has('rawGraph')) {
|
||||
return null;
|
||||
}
|
||||
handleRawGraphQueryParam(url.searchParams);
|
||||
return redirect(`${url.pathname}?${url.searchParams.toString()}`);
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'tasks',
|
||||
id: 'tasks',
|
||||
...tasksRouteObject,
|
||||
},
|
||||
{
|
||||
path: 'tasks/all',
|
||||
id: 'tasksAll',
|
||||
...tasksRouteObject,
|
||||
},
|
||||
];
|
||||
|
||||
function handleRawGraphQueryParam(searchParams: URLSearchParams) {
|
||||
const rawGraph = searchParams.get('rawGraph');
|
||||
|
||||
// nothing to do here, bail
|
||||
if (!rawGraph) return;
|
||||
|
||||
try {
|
||||
const rawGraphJson: {
|
||||
config: Record<string, unknown>;
|
||||
state?: Record<string, unknown>;
|
||||
} = JSON.parse(rawGraph);
|
||||
|
||||
searchParams.delete('rawGraph');
|
||||
searchParams.set(
|
||||
'graph',
|
||||
GraphStateSerializer.serialize({
|
||||
c: rawGraphJson.config,
|
||||
s: rawGraphJson.state,
|
||||
} as Omit<GraphSerializableState<any>, 'v'>)
|
||||
);
|
||||
} catch (err) {
|
||||
console.error('Graph Client: error during rawGraph handling', err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
export const devRoutes: RouteObject[] = [
|
||||
{
|
||||
path: '/',
|
||||
errorElement: <ErrorBoundary />,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
loader: ({ request }) => {
|
||||
const { searchParams } = new URL(request.url);
|
||||
handleRawGraphQueryParam(searchParams);
|
||||
return redirect(
|
||||
`/${
|
||||
appConfig.defaultWorkspaceId
|
||||
}/projects?${searchParams.toString()}`
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
path: ':selectedWorkspaceId',
|
||||
id: 'selectedWorkspace',
|
||||
shouldRevalidate: ({ currentParams, nextParams }) => {
|
||||
return (
|
||||
currentParams.selectedWorkspaceId !== nextParams.selectedWorkspaceId
|
||||
);
|
||||
},
|
||||
loader: async ({ params }) => {
|
||||
const selectedWorkspaceId =
|
||||
params.selectedWorkspaceId ?? appConfig.defaultWorkspaceId;
|
||||
return workspaceDataLoader(selectedWorkspaceId, true);
|
||||
},
|
||||
children: childRoutes,
|
||||
},
|
||||
{
|
||||
path: ':selectedWorkspaceId/project-details/:projectName',
|
||||
id: 'selectedProjectDetails',
|
||||
element: <ProjectDetailsPage />,
|
||||
loader: async ({ params }) => {
|
||||
const projectName = params.projectName;
|
||||
return projectDetailsLoader(params.selectedWorkspaceId, projectName);
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const releaseRoutes: RouteObject[] = [
|
||||
{
|
||||
path: '/',
|
||||
id: 'selectedWorkspace',
|
||||
loader: async () => {
|
||||
const selectedWorkspaceId = appConfig.defaultWorkspaceId;
|
||||
return workspaceDataLoader(selectedWorkspaceId, true);
|
||||
},
|
||||
shouldRevalidate: () => {
|
||||
return false;
|
||||
},
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
loader: ({ request }) => {
|
||||
const { searchParams } = new URL(request.url);
|
||||
handleRawGraphQueryParam(searchParams);
|
||||
return redirect(`/projects?${searchParams.toString()}`);
|
||||
},
|
||||
},
|
||||
...childRoutes,
|
||||
],
|
||||
errorElement: <ErrorBoundary />,
|
||||
},
|
||||
{
|
||||
path: 'project-details/:projectName',
|
||||
id: 'selectedProjectDetails',
|
||||
element: <ProjectDetailsPage />,
|
||||
errorElement: <ErrorBoundary />,
|
||||
loader: async ({ params }) => {
|
||||
const projectName = params.projectName;
|
||||
return projectDetailsLoader(appConfig.defaultWorkspaceId, projectName);
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,153 @@
|
||||
// nx-ignore-next-line
|
||||
import type { TaskGraphClientResponse } from 'nx/src/command-line/graph/graph';
|
||||
|
||||
class TaskGraphClientCache {
|
||||
private cached: TaskGraphClientResponse | null = null;
|
||||
private currentWorkspaceId: string | null = null;
|
||||
private fetchedSet = new Set<string>();
|
||||
|
||||
setWorkspace(workspaceId: string) {
|
||||
if (this.currentWorkspaceId === workspaceId) return;
|
||||
this.currentWorkspaceId = workspaceId;
|
||||
this.clear();
|
||||
}
|
||||
|
||||
getCached(targets: string[], requestedProjects?: string[]) {
|
||||
// if we don't have cached result yet, we'll always fetch
|
||||
if (!this.cached) {
|
||||
return {
|
||||
cached: null,
|
||||
missingTargets: targets,
|
||||
missingProjects: requestedProjects,
|
||||
};
|
||||
}
|
||||
|
||||
// this means we're fetching "all" for all targets
|
||||
if (!requestedProjects || requestedProjects.length === 0) {
|
||||
const missingTargets: string[] = [];
|
||||
for (const target of targets) {
|
||||
const targetWithAllKey = `${target}:all`;
|
||||
// if we already fetch for this target, skip
|
||||
if (this.fetchedSet.has(targetWithAllKey)) continue;
|
||||
missingTargets.push(target);
|
||||
}
|
||||
|
||||
// no missing targets needed, we return the cached
|
||||
return {
|
||||
cached: missingTargets.length === 0 ? this.cached : null,
|
||||
missingTargets,
|
||||
};
|
||||
}
|
||||
|
||||
const missingTargetsSet = new Set<string>();
|
||||
const missingProjectsSet = new Set<string>();
|
||||
|
||||
for (const target of targets) {
|
||||
for (const project of requestedProjects) {
|
||||
const key = `${target}:${project}`;
|
||||
if (this.fetchedSet.has(key)) continue;
|
||||
|
||||
missingTargetsSet.add(target);
|
||||
missingProjectsSet.add(project);
|
||||
}
|
||||
}
|
||||
|
||||
if (missingTargetsSet.size === 0 && missingProjectsSet.size === 0) {
|
||||
return { cached: this.cached, missingTargets: [] };
|
||||
}
|
||||
|
||||
return {
|
||||
cached: null,
|
||||
missingTargets: Array.from(missingTargetsSet),
|
||||
missingProjects: Array.from(missingProjectsSet),
|
||||
};
|
||||
}
|
||||
|
||||
mergeTaskGraph(
|
||||
response: TaskGraphClientResponse,
|
||||
fetchedTargets: string[],
|
||||
fetchedProjects?: string[]
|
||||
) {
|
||||
// populate the fetchedSet with targets and projects that were used
|
||||
// in the request
|
||||
if (!fetchedProjects) {
|
||||
fetchedTargets.forEach((fetchedTarget) => {
|
||||
this.fetchedSet.add(`${fetchedTarget}:all`);
|
||||
});
|
||||
} else {
|
||||
for (const fetchedTarget of fetchedTargets) {
|
||||
for (const fetchedProject of fetchedProjects) {
|
||||
this.fetchedSet.add(`${fetchedTarget}:${fetchedProject}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const mergedTasks = {
|
||||
...this.cached?.taskGraph.tasks,
|
||||
...response.taskGraph.tasks,
|
||||
};
|
||||
|
||||
const rootsSet = new Set<string>(this.cached?.taskGraph.roots || []);
|
||||
for (const root of response.taskGraph.roots) {
|
||||
rootsSet.add(root);
|
||||
}
|
||||
|
||||
const mergedDependencies = this.mergeStringCollectionRecord(
|
||||
this.cached?.taskGraph.dependencies,
|
||||
response.taskGraph.dependencies
|
||||
);
|
||||
|
||||
const mergedContinuousDependencies = this.mergeStringCollectionRecord(
|
||||
this.cached?.taskGraph.continuousDependencies,
|
||||
response.taskGraph.continuousDependencies
|
||||
);
|
||||
|
||||
const mergedTaskGraph = {
|
||||
tasks: mergedTasks,
|
||||
roots: Array.from(rootsSet),
|
||||
dependencies: mergedDependencies,
|
||||
continuousDependencies: mergedContinuousDependencies,
|
||||
};
|
||||
|
||||
const mergedPlans = this.mergeStringCollectionRecord(
|
||||
this.cached?.plans,
|
||||
response.plans
|
||||
);
|
||||
|
||||
this.cached = {
|
||||
taskGraph: mergedTaskGraph,
|
||||
plans: mergedPlans,
|
||||
error: response.error || this.cached?.error || null,
|
||||
};
|
||||
|
||||
return this.cached;
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.cached = null;
|
||||
this.fetchedSet.clear();
|
||||
}
|
||||
|
||||
private mergeStringCollectionRecord(
|
||||
cachedRecord?: Record<string, string[]>,
|
||||
responseRecord?: Record<string, string[]>
|
||||
) {
|
||||
const mergedRecord: Record<string, string[]> = {};
|
||||
|
||||
for (const taskId in cachedRecord) {
|
||||
mergedRecord[taskId] = cachedRecord[taskId];
|
||||
}
|
||||
|
||||
for (const taskId in responseRecord) {
|
||||
const collectionSet = new Set([
|
||||
...(mergedRecord[taskId] || []),
|
||||
...(responseRecord[taskId] || []),
|
||||
]);
|
||||
mergedRecord[taskId] = Array.from(collectionSet);
|
||||
}
|
||||
|
||||
return mergedRecord;
|
||||
}
|
||||
}
|
||||
|
||||
export const taskGraphClientCache = new TaskGraphClientCache();
|
||||
@@ -0,0 +1,73 @@
|
||||
import { ProjectDetailsHeader } from '@nx/graph-internal-project-details';
|
||||
import {
|
||||
fetchProjectGraph,
|
||||
getProjectGraphDataService,
|
||||
useEnvironmentConfig,
|
||||
usePoll,
|
||||
} from '@nx/graph-shared';
|
||||
import {
|
||||
isRouteErrorResponse,
|
||||
useParams,
|
||||
useRouteError,
|
||||
} from 'react-router-dom';
|
||||
import { ErrorPage } from './error-page';
|
||||
|
||||
export function ErrorBoundary() {
|
||||
let error = useRouteError();
|
||||
console.error(error);
|
||||
|
||||
const { environment, appConfig, watch } = useEnvironmentConfig();
|
||||
const projectGraphDataService = getProjectGraphDataService();
|
||||
const params = useParams();
|
||||
|
||||
const hasErrorData =
|
||||
isRouteErrorResponse(error) && error.data.errors?.length > 0;
|
||||
|
||||
usePoll(
|
||||
async () => {
|
||||
const data = await fetchProjectGraph(
|
||||
projectGraphDataService,
|
||||
params,
|
||||
appConfig
|
||||
);
|
||||
if (
|
||||
isRouteErrorResponse(error) &&
|
||||
error.data.id === 'project-not-found' &&
|
||||
data.projects.find((p) => p.name === error.data.projectName)
|
||||
) {
|
||||
window.location.reload();
|
||||
}
|
||||
},
|
||||
1000,
|
||||
watch
|
||||
);
|
||||
|
||||
let message: string | JSX.Element;
|
||||
let stack: string;
|
||||
if (isRouteErrorResponse(error) && error.data.id === 'project-not-found') {
|
||||
message = (
|
||||
<p>
|
||||
Project <code>{error.data.projectName}</code> not found.
|
||||
</p>
|
||||
);
|
||||
} else {
|
||||
message = 'Disconnected from graph server. ';
|
||||
if (environment === 'nx-console') {
|
||||
message += 'Please refresh the page.';
|
||||
} else {
|
||||
message += 'Please rerun your command and refresh the page.';
|
||||
}
|
||||
stack = error.toString();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-screen w-full flex-col items-center">
|
||||
{environment !== 'nx-console' && <ProjectDetailsHeader />}
|
||||
<ErrorPage
|
||||
message={message}
|
||||
stack={stack}
|
||||
errors={hasErrorData ? error.data.errors : undefined}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// nx-ignore-next-line
|
||||
import { ErrorRenderer } from '@nx/graph-ui-common';
|
||||
// nx-ignore-next-line
|
||||
import { GraphError } from 'nx/src/command-line/graph/graph';
|
||||
|
||||
import type { JSX } from 'react';
|
||||
|
||||
export type ErrorPageProps = {
|
||||
message: string | JSX.Element;
|
||||
stack?: string;
|
||||
errors: GraphError[];
|
||||
};
|
||||
|
||||
export function ErrorPage({ message, stack, errors }: ErrorPageProps) {
|
||||
return (
|
||||
<div className="mx-auto mb-8 w-full max-w-6xl flex-grow px-8">
|
||||
<h1 className="mb-4 text-4xl dark:text-slate-100">Error</h1>
|
||||
<div>
|
||||
<ErrorWithStack message={message} stack={stack} />
|
||||
</div>
|
||||
{errors && (
|
||||
<div>
|
||||
<p className="text-md mb-4 dark:text-slate-200">
|
||||
Nx encountered the following issues while processing the project
|
||||
graph:{' '}
|
||||
</p>
|
||||
<div>
|
||||
<ErrorRenderer errors={errors} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ErrorWithStack({
|
||||
message,
|
||||
stack,
|
||||
}: {
|
||||
message: string | JSX.Element;
|
||||
stack?: string;
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
<p className="mb-4 text-lg dark:text-slate-100">{message}</p>
|
||||
{stack && <p className="text-sm">Error message: {stack}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// nx-ignore-next-line
|
||||
import type { FileDataDependency } from 'nx/src/config/project-graph';
|
||||
|
||||
export function createTaskName(
|
||||
project: string,
|
||||
target: string,
|
||||
configuration?: string
|
||||
) {
|
||||
if (configuration) {
|
||||
return `task-${project}:${target}:${configuration}`;
|
||||
} else {
|
||||
return `task-${project}:${target}`;
|
||||
}
|
||||
}
|
||||
|
||||
export function extractDependencyTarget(dependency: FileDataDependency) {
|
||||
if (typeof dependency === 'string') return dependency;
|
||||
if (dependency.length === 2) return dependency[0];
|
||||
return dependency[1];
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
window.exclude = [];
|
||||
window.watch = false;
|
||||
window.environment = 'dev';
|
||||
window.useXstateInspect = false;
|
||||
|
||||
window.appConfig = {
|
||||
showDebugger: true,
|
||||
showExperimentalFeatures: true,
|
||||
workspaces: [
|
||||
{
|
||||
id: 'e2e',
|
||||
label: 'e2e',
|
||||
projectGraphUrl: 'assets/project-graphs/e2e.json',
|
||||
taskGraphUrl: 'assets/task-graphs/e2e.json',
|
||||
taskInputsUrl: 'assets/task-inputs/e2e.json',
|
||||
sourceMapsUrl: 'assets/source-maps/e2e.json',
|
||||
},
|
||||
{
|
||||
id: 'affected',
|
||||
label: 'affected',
|
||||
projectGraphUrl: 'assets/project-graphs/affected.json',
|
||||
taskGraphUrl: 'assets/task-graphs/affected.json',
|
||||
taskInputsUrl: 'assets/task-inputs/affected.json',
|
||||
sourceMapsUrl: 'assets/source-maps/e2e-affected.json',
|
||||
},
|
||||
],
|
||||
defaultWorkspaceId: 'e2e',
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
window.exclude = [];
|
||||
window.watch = false;
|
||||
window.environment = 'nx-console';
|
||||
window.useXstateInspect = false;
|
||||
|
||||
window.appConfig = {
|
||||
showDebugger: false,
|
||||
showExperimentalFeatures: false,
|
||||
workspaces: [
|
||||
{
|
||||
id: 'local',
|
||||
label: 'local',
|
||||
projectGraphUrl: 'assets/project-graphs/e2e.json',
|
||||
taskGraphUrl: 'assets/task-graphs/e2e.json',
|
||||
},
|
||||
],
|
||||
defaultWorkspaceId: 'local',
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,20 @@
|
||||
window.exclude = [];
|
||||
window.watch = false;
|
||||
window.environment = 'release';
|
||||
window.useXstateInspect = false;
|
||||
|
||||
window.appConfig = {
|
||||
showDebugger: false,
|
||||
showExperimentalFeatures: false,
|
||||
workspaces: [
|
||||
{
|
||||
id: 'local',
|
||||
label: 'local',
|
||||
projectGraphUrl: 'assets/project-graphs/e2e.json',
|
||||
taskGraphUrl: 'assets/task-graphs/e2e.json',
|
||||
taskInputsUrl: 'assets/task-inputs/e2e.json',
|
||||
sourceMapsUrl: 'assets/source-maps/e2e.json',
|
||||
},
|
||||
],
|
||||
defaultWorkspaceId: 'local',
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,450 @@
|
||||
{
|
||||
"taskGraph": {
|
||||
"tasks": {
|
||||
"products-product-detail-page:lint": {
|
||||
"id": "products-product-detail-page:lint",
|
||||
"target": {
|
||||
"project": "products-product-detail-page",
|
||||
"target": "lint"
|
||||
},
|
||||
"projectRoot": "libs/products/product-detail-page",
|
||||
"overrides": {}
|
||||
},
|
||||
"products-product-detail-page:test": {
|
||||
"id": "products-product-detail-page:test",
|
||||
"target": {
|
||||
"project": "products-product-detail-page",
|
||||
"target": "test"
|
||||
},
|
||||
"projectRoot": "libs/products/product-detail-page",
|
||||
"overrides": {}
|
||||
},
|
||||
"shared-product-state:lint": {
|
||||
"id": "shared-product-state:lint",
|
||||
"target": {
|
||||
"project": "shared-product-state",
|
||||
"target": "lint"
|
||||
},
|
||||
"projectRoot": "libs/shared/product/state",
|
||||
"overrides": {}
|
||||
},
|
||||
"shared-product-state:test": {
|
||||
"id": "shared-product-state:test",
|
||||
"target": {
|
||||
"project": "shared-product-state",
|
||||
"target": "test"
|
||||
},
|
||||
"projectRoot": "libs/shared/product/state",
|
||||
"overrides": {}
|
||||
},
|
||||
"shared-product-types:lint": {
|
||||
"id": "shared-product-types:lint",
|
||||
"target": {
|
||||
"project": "shared-product-types",
|
||||
"target": "lint"
|
||||
},
|
||||
"projectRoot": "libs/shared/product/types",
|
||||
"overrides": {}
|
||||
},
|
||||
"shared-product-data:lint": {
|
||||
"id": "shared-product-data:lint",
|
||||
"target": {
|
||||
"project": "shared-product-data",
|
||||
"target": "lint"
|
||||
},
|
||||
"projectRoot": "libs/shared/product/data",
|
||||
"overrides": {}
|
||||
},
|
||||
"products-home-page:lint": {
|
||||
"id": "products-home-page:lint",
|
||||
"target": {
|
||||
"project": "products-home-page",
|
||||
"target": "lint"
|
||||
},
|
||||
"projectRoot": "libs/products/home-page",
|
||||
"overrides": {}
|
||||
},
|
||||
"products-home-page:test": {
|
||||
"id": "products-home-page:test",
|
||||
"target": {
|
||||
"project": "products-home-page",
|
||||
"target": "test"
|
||||
},
|
||||
"projectRoot": "libs/products/home-page",
|
||||
"overrides": {}
|
||||
},
|
||||
"shared-cart-state:lint": {
|
||||
"id": "shared-cart-state:lint",
|
||||
"target": {
|
||||
"project": "shared-cart-state",
|
||||
"target": "lint"
|
||||
},
|
||||
"projectRoot": "libs/shared/cart/state",
|
||||
"overrides": {}
|
||||
},
|
||||
"shared-cart-state:test": {
|
||||
"id": "shared-cart-state:test",
|
||||
"target": {
|
||||
"project": "shared-cart-state",
|
||||
"target": "test"
|
||||
},
|
||||
"projectRoot": "libs/shared/cart/state",
|
||||
"overrides": {}
|
||||
},
|
||||
"shared-product-ui:lint": {
|
||||
"id": "shared-product-ui:lint",
|
||||
"target": {
|
||||
"project": "shared-product-ui",
|
||||
"target": "lint"
|
||||
},
|
||||
"projectRoot": "libs/shared/product/ui",
|
||||
"overrides": {}
|
||||
},
|
||||
"shared-product-ui:test": {
|
||||
"id": "shared-product-ui:test",
|
||||
"target": {
|
||||
"project": "shared-product-ui",
|
||||
"target": "test"
|
||||
},
|
||||
"projectRoot": "libs/shared/product/ui",
|
||||
"overrides": {}
|
||||
},
|
||||
"shared-e2e-utils:lint": {
|
||||
"id": "shared-e2e-utils:lint",
|
||||
"target": {
|
||||
"project": "shared-e2e-utils",
|
||||
"target": "lint"
|
||||
},
|
||||
"projectRoot": "libs/shared/e2e-utils",
|
||||
"overrides": {}
|
||||
},
|
||||
"cart-cart-page:lint": {
|
||||
"id": "cart-cart-page:lint",
|
||||
"target": {
|
||||
"project": "cart-cart-page",
|
||||
"target": "lint"
|
||||
},
|
||||
"projectRoot": "libs/cart/cart-page",
|
||||
"overrides": {}
|
||||
},
|
||||
"cart-cart-page:test": {
|
||||
"id": "cart-cart-page:test",
|
||||
"target": {
|
||||
"project": "cart-cart-page",
|
||||
"target": "test"
|
||||
},
|
||||
"projectRoot": "libs/cart/cart-page",
|
||||
"overrides": {}
|
||||
},
|
||||
"shared-header:lint": {
|
||||
"id": "shared-header:lint",
|
||||
"target": {
|
||||
"project": "shared-header",
|
||||
"target": "lint"
|
||||
},
|
||||
"projectRoot": "libs/shared/header",
|
||||
"overrides": {}
|
||||
},
|
||||
"shared-header:test": {
|
||||
"id": "shared-header:test",
|
||||
"target": {
|
||||
"project": "shared-header",
|
||||
"target": "test"
|
||||
},
|
||||
"projectRoot": "libs/shared/header",
|
||||
"overrides": {}
|
||||
},
|
||||
"shared-jsxify:lint": {
|
||||
"id": "shared-jsxify:lint",
|
||||
"target": {
|
||||
"project": "shared-jsxify",
|
||||
"target": "lint"
|
||||
},
|
||||
"projectRoot": "libs/shared/jsxify",
|
||||
"overrides": {}
|
||||
},
|
||||
"products-e2e:e2e": {
|
||||
"id": "products-e2e:e2e",
|
||||
"target": {
|
||||
"project": "products-e2e",
|
||||
"target": "e2e"
|
||||
},
|
||||
"projectRoot": "apps/products-e2e",
|
||||
"overrides": {}
|
||||
},
|
||||
"products-e2e:e2e:production": {
|
||||
"id": "products-e2e:e2e:production",
|
||||
"target": {
|
||||
"project": "products-e2e",
|
||||
"target": "e2e",
|
||||
"configuration": "production"
|
||||
},
|
||||
"projectRoot": "apps/products-e2e",
|
||||
"overrides": {}
|
||||
},
|
||||
"products-e2e:lint": {
|
||||
"id": "products-e2e:lint",
|
||||
"target": {
|
||||
"project": "products-e2e",
|
||||
"target": "lint"
|
||||
},
|
||||
"projectRoot": "apps/products-e2e",
|
||||
"overrides": {}
|
||||
},
|
||||
"cart-e2e:e2e": {
|
||||
"id": "cart-e2e:e2e",
|
||||
"target": {
|
||||
"project": "cart-e2e",
|
||||
"target": "e2e"
|
||||
},
|
||||
"projectRoot": "apps/cart-e2e",
|
||||
"overrides": {}
|
||||
},
|
||||
"cart-e2e:e2e:production": {
|
||||
"id": "cart-e2e:e2e:production",
|
||||
"target": {
|
||||
"project": "cart-e2e",
|
||||
"target": "e2e",
|
||||
"configuration": "production"
|
||||
},
|
||||
"projectRoot": "apps/cart-e2e",
|
||||
"overrides": {}
|
||||
},
|
||||
"cart-e2e:lint": {
|
||||
"id": "cart-e2e:lint",
|
||||
"target": {
|
||||
"project": "cart-e2e",
|
||||
"target": "lint"
|
||||
},
|
||||
"projectRoot": "apps/cart-e2e",
|
||||
"overrides": {}
|
||||
},
|
||||
"products:build": {
|
||||
"id": "products:build",
|
||||
"target": {
|
||||
"project": "products",
|
||||
"target": "build"
|
||||
},
|
||||
"projectRoot": "apps/products",
|
||||
"overrides": {}
|
||||
},
|
||||
"products:build:production": {
|
||||
"id": "products:build:production",
|
||||
"target": {
|
||||
"project": "products",
|
||||
"target": "build",
|
||||
"configuration": "production"
|
||||
},
|
||||
"projectRoot": "apps/products",
|
||||
"overrides": {}
|
||||
},
|
||||
"products:serve": {
|
||||
"id": "products:serve",
|
||||
"target": {
|
||||
"project": "products",
|
||||
"target": "serve"
|
||||
},
|
||||
"projectRoot": "apps/products",
|
||||
"overrides": {}
|
||||
},
|
||||
"products:serve:production": {
|
||||
"id": "products:serve:production",
|
||||
"target": {
|
||||
"project": "products",
|
||||
"target": "serve",
|
||||
"configuration": "production"
|
||||
},
|
||||
"projectRoot": "apps/products",
|
||||
"overrides": {}
|
||||
},
|
||||
"products:extract-i18n": {
|
||||
"id": "products:extract-i18n",
|
||||
"target": {
|
||||
"project": "products",
|
||||
"target": "extract-i18n"
|
||||
},
|
||||
"projectRoot": "apps/products",
|
||||
"overrides": {}
|
||||
},
|
||||
"products:lint": {
|
||||
"id": "products:lint",
|
||||
"target": {
|
||||
"project": "products",
|
||||
"target": "lint"
|
||||
},
|
||||
"projectRoot": "apps/products",
|
||||
"overrides": {}
|
||||
},
|
||||
"products:test": {
|
||||
"id": "products:test",
|
||||
"target": {
|
||||
"project": "products",
|
||||
"target": "test"
|
||||
},
|
||||
"projectRoot": "apps/products",
|
||||
"overrides": {}
|
||||
},
|
||||
"products:deploy": {
|
||||
"id": "products:deploy",
|
||||
"target": {
|
||||
"project": "products",
|
||||
"target": "deploy"
|
||||
},
|
||||
"projectRoot": "apps/products",
|
||||
"overrides": {}
|
||||
},
|
||||
"cart:build:production": {
|
||||
"id": "cart:build:production",
|
||||
"target": {
|
||||
"project": "cart",
|
||||
"target": "build",
|
||||
"configuration": "production"
|
||||
},
|
||||
"projectRoot": "apps/cart",
|
||||
"overrides": {}
|
||||
},
|
||||
"cart:build:development": {
|
||||
"id": "cart:build:development",
|
||||
"target": {
|
||||
"project": "cart",
|
||||
"target": "build",
|
||||
"configuration": "development"
|
||||
},
|
||||
"projectRoot": "apps/cart",
|
||||
"overrides": {}
|
||||
},
|
||||
"cart:serve:development": {
|
||||
"id": "cart:serve:development",
|
||||
"target": {
|
||||
"project": "cart",
|
||||
"target": "serve",
|
||||
"configuration": "development"
|
||||
},
|
||||
"projectRoot": "apps/cart",
|
||||
"overrides": {}
|
||||
},
|
||||
"cart:serve:production": {
|
||||
"id": "cart:serve:production",
|
||||
"target": {
|
||||
"project": "cart",
|
||||
"target": "serve",
|
||||
"configuration": "production"
|
||||
},
|
||||
"projectRoot": "apps/cart",
|
||||
"overrides": {}
|
||||
},
|
||||
"cart:lint": {
|
||||
"id": "cart:lint",
|
||||
"target": {
|
||||
"project": "cart",
|
||||
"target": "lint"
|
||||
},
|
||||
"projectRoot": "apps/cart",
|
||||
"overrides": {}
|
||||
},
|
||||
"cart:test": {
|
||||
"id": "cart:test",
|
||||
"target": {
|
||||
"project": "cart",
|
||||
"target": "test"
|
||||
},
|
||||
"projectRoot": "apps/cart",
|
||||
"overrides": {}
|
||||
},
|
||||
"cart:deploy": {
|
||||
"id": "cart:deploy",
|
||||
"target": {
|
||||
"project": "cart",
|
||||
"target": "deploy"
|
||||
},
|
||||
"projectRoot": "apps/cart",
|
||||
"overrides": {}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"products-product-detail-page:lint": [],
|
||||
"products-product-detail-page:test": [],
|
||||
"shared-product-state:lint": [],
|
||||
"shared-product-state:test": [],
|
||||
"shared-product-types:lint": [],
|
||||
"shared-product-data:lint": [],
|
||||
"products-home-page:lint": [],
|
||||
"products-home-page:test": [],
|
||||
"shared-cart-state:lint": [],
|
||||
"shared-cart-state:test": [],
|
||||
"shared-product-ui:lint": [],
|
||||
"shared-product-ui:test": [],
|
||||
"shared-e2e-utils:lint": [],
|
||||
"cart-cart-page:lint": [],
|
||||
"cart-cart-page:test": [],
|
||||
"shared-header:lint": [],
|
||||
"shared-header:test": [],
|
||||
"shared-jsxify:lint": [],
|
||||
"products-e2e:e2e": [],
|
||||
"products-e2e:e2e:production": [],
|
||||
"products-e2e:lint": [],
|
||||
"cart-e2e:e2e": [],
|
||||
"cart-e2e:e2e:production": [],
|
||||
"cart-e2e:lint": [],
|
||||
"products:build": [],
|
||||
"products:build:production": [],
|
||||
"products:serve": [],
|
||||
"products:serve:production": [],
|
||||
"products:extract-i18n": [],
|
||||
"products:lint": [],
|
||||
"products:test": [],
|
||||
"products:deploy": [],
|
||||
"cart:build:production": [],
|
||||
"cart:build:development": [],
|
||||
"cart:serve:development": [],
|
||||
"cart:serve:production": [],
|
||||
"cart:lint": [],
|
||||
"cart:test": [],
|
||||
"cart:deploy": []
|
||||
},
|
||||
"continuousDependencies": {},
|
||||
"roots": [
|
||||
"products-product-detail-page:lint",
|
||||
"products-product-detail-page:test",
|
||||
"shared-product-state:lint",
|
||||
"shared-product-state:test",
|
||||
"shared-product-types:lint",
|
||||
"shared-product-data:lint",
|
||||
"products-home-page:lint",
|
||||
"products-home-page:test",
|
||||
"shared-cart-state:lint",
|
||||
"shared-cart-state:test",
|
||||
"shared-product-ui:lint",
|
||||
"shared-product-ui:test",
|
||||
"shared-e2e-utils:lint",
|
||||
"cart-cart-page:lint",
|
||||
"cart-cart-page:test",
|
||||
"shared-header:lint",
|
||||
"shared-header:test",
|
||||
"shared-jsxify:lint",
|
||||
"products-e2e:e2e",
|
||||
"products-e2e:e2e:production",
|
||||
"products-e2e:lint",
|
||||
"cart-e2e:e2e",
|
||||
"cart-e2e:e2e:production",
|
||||
"cart-e2e:lint",
|
||||
"products:build",
|
||||
"products:build:production",
|
||||
"products:serve",
|
||||
"products:serve:production",
|
||||
"products:extract-i18n",
|
||||
"products:lint",
|
||||
"products:test",
|
||||
"products:deploy",
|
||||
"cart:build:production",
|
||||
"cart:build:development",
|
||||
"cart:serve:development",
|
||||
"cart:serve:production",
|
||||
"cart:lint",
|
||||
"cart:test",
|
||||
"cart:deploy"
|
||||
]
|
||||
},
|
||||
"plans": {},
|
||||
"error": null
|
||||
}
|
||||
@@ -0,0 +1,450 @@
|
||||
{
|
||||
"taskGraph": {
|
||||
"tasks": {
|
||||
"products-product-detail-page:lint": {
|
||||
"id": "products-product-detail-page:lint",
|
||||
"target": {
|
||||
"project": "products-product-detail-page",
|
||||
"target": "lint"
|
||||
},
|
||||
"projectRoot": "libs/products/product-detail-page",
|
||||
"overrides": {}
|
||||
},
|
||||
"products-product-detail-page:test": {
|
||||
"id": "products-product-detail-page:test",
|
||||
"target": {
|
||||
"project": "products-product-detail-page",
|
||||
"target": "test"
|
||||
},
|
||||
"projectRoot": "libs/products/product-detail-page",
|
||||
"overrides": {}
|
||||
},
|
||||
"shared-product-state:lint": {
|
||||
"id": "shared-product-state:lint",
|
||||
"target": {
|
||||
"project": "shared-product-state",
|
||||
"target": "lint"
|
||||
},
|
||||
"projectRoot": "libs/shared/product/state",
|
||||
"overrides": {}
|
||||
},
|
||||
"shared-product-state:test": {
|
||||
"id": "shared-product-state:test",
|
||||
"target": {
|
||||
"project": "shared-product-state",
|
||||
"target": "test"
|
||||
},
|
||||
"projectRoot": "libs/shared/product/state",
|
||||
"overrides": {}
|
||||
},
|
||||
"shared-product-types:lint": {
|
||||
"id": "shared-product-types:lint",
|
||||
"target": {
|
||||
"project": "shared-product-types",
|
||||
"target": "lint"
|
||||
},
|
||||
"projectRoot": "libs/shared/product/types",
|
||||
"overrides": {}
|
||||
},
|
||||
"shared-product-data:lint": {
|
||||
"id": "shared-product-data:lint",
|
||||
"target": {
|
||||
"project": "shared-product-data",
|
||||
"target": "lint"
|
||||
},
|
||||
"projectRoot": "libs/shared/product/data",
|
||||
"overrides": {}
|
||||
},
|
||||
"products-home-page:lint": {
|
||||
"id": "products-home-page:lint",
|
||||
"target": {
|
||||
"project": "products-home-page",
|
||||
"target": "lint"
|
||||
},
|
||||
"projectRoot": "libs/products/home-page",
|
||||
"overrides": {}
|
||||
},
|
||||
"products-home-page:test": {
|
||||
"id": "products-home-page:test",
|
||||
"target": {
|
||||
"project": "products-home-page",
|
||||
"target": "test"
|
||||
},
|
||||
"projectRoot": "libs/products/home-page",
|
||||
"overrides": {}
|
||||
},
|
||||
"shared-cart-state:lint": {
|
||||
"id": "shared-cart-state:lint",
|
||||
"target": {
|
||||
"project": "shared-cart-state",
|
||||
"target": "lint"
|
||||
},
|
||||
"projectRoot": "libs/shared/cart/state",
|
||||
"overrides": {}
|
||||
},
|
||||
"shared-cart-state:test": {
|
||||
"id": "shared-cart-state:test",
|
||||
"target": {
|
||||
"project": "shared-cart-state",
|
||||
"target": "test"
|
||||
},
|
||||
"projectRoot": "libs/shared/cart/state",
|
||||
"overrides": {}
|
||||
},
|
||||
"shared-product-ui:lint": {
|
||||
"id": "shared-product-ui:lint",
|
||||
"target": {
|
||||
"project": "shared-product-ui",
|
||||
"target": "lint"
|
||||
},
|
||||
"projectRoot": "libs/shared/product/ui",
|
||||
"overrides": {}
|
||||
},
|
||||
"shared-product-ui:test": {
|
||||
"id": "shared-product-ui:test",
|
||||
"target": {
|
||||
"project": "shared-product-ui",
|
||||
"target": "test"
|
||||
},
|
||||
"projectRoot": "libs/shared/product/ui",
|
||||
"overrides": {}
|
||||
},
|
||||
"shared-e2e-utils:lint": {
|
||||
"id": "shared-e2e-utils:lint",
|
||||
"target": {
|
||||
"project": "shared-e2e-utils",
|
||||
"target": "lint"
|
||||
},
|
||||
"projectRoot": "libs/shared/e2e-utils",
|
||||
"overrides": {}
|
||||
},
|
||||
"cart-cart-page:lint": {
|
||||
"id": "cart-cart-page:lint",
|
||||
"target": {
|
||||
"project": "cart-cart-page",
|
||||
"target": "lint"
|
||||
},
|
||||
"projectRoot": "libs/cart/cart-page",
|
||||
"overrides": {}
|
||||
},
|
||||
"cart-cart-page:test": {
|
||||
"id": "cart-cart-page:test",
|
||||
"target": {
|
||||
"project": "cart-cart-page",
|
||||
"target": "test"
|
||||
},
|
||||
"projectRoot": "libs/cart/cart-page",
|
||||
"overrides": {}
|
||||
},
|
||||
"shared-header:lint": {
|
||||
"id": "shared-header:lint",
|
||||
"target": {
|
||||
"project": "shared-header",
|
||||
"target": "lint"
|
||||
},
|
||||
"projectRoot": "libs/shared/header",
|
||||
"overrides": {}
|
||||
},
|
||||
"shared-header:test": {
|
||||
"id": "shared-header:test",
|
||||
"target": {
|
||||
"project": "shared-header",
|
||||
"target": "test"
|
||||
},
|
||||
"projectRoot": "libs/shared/header",
|
||||
"overrides": {}
|
||||
},
|
||||
"shared-jsxify:lint": {
|
||||
"id": "shared-jsxify:lint",
|
||||
"target": {
|
||||
"project": "shared-jsxify",
|
||||
"target": "lint"
|
||||
},
|
||||
"projectRoot": "libs/shared/jsxify",
|
||||
"overrides": {}
|
||||
},
|
||||
"products-e2e:e2e": {
|
||||
"id": "products-e2e:e2e",
|
||||
"target": {
|
||||
"project": "products-e2e",
|
||||
"target": "e2e"
|
||||
},
|
||||
"projectRoot": "apps/products-e2e",
|
||||
"overrides": {}
|
||||
},
|
||||
"products-e2e:e2e:production": {
|
||||
"id": "products-e2e:e2e:production",
|
||||
"target": {
|
||||
"project": "products-e2e",
|
||||
"target": "e2e",
|
||||
"configuration": "production"
|
||||
},
|
||||
"projectRoot": "apps/products-e2e",
|
||||
"overrides": {}
|
||||
},
|
||||
"products-e2e:lint": {
|
||||
"id": "products-e2e:lint",
|
||||
"target": {
|
||||
"project": "products-e2e",
|
||||
"target": "lint"
|
||||
},
|
||||
"projectRoot": "apps/products-e2e",
|
||||
"overrides": {}
|
||||
},
|
||||
"cart-e2e:e2e": {
|
||||
"id": "cart-e2e:e2e",
|
||||
"target": {
|
||||
"project": "cart-e2e",
|
||||
"target": "e2e"
|
||||
},
|
||||
"projectRoot": "apps/cart-e2e",
|
||||
"overrides": {}
|
||||
},
|
||||
"cart-e2e:e2e:production": {
|
||||
"id": "cart-e2e:e2e:production",
|
||||
"target": {
|
||||
"project": "cart-e2e",
|
||||
"target": "e2e",
|
||||
"configuration": "production"
|
||||
},
|
||||
"projectRoot": "apps/cart-e2e",
|
||||
"overrides": {}
|
||||
},
|
||||
"cart-e2e:lint": {
|
||||
"id": "cart-e2e:lint",
|
||||
"target": {
|
||||
"project": "cart-e2e",
|
||||
"target": "lint"
|
||||
},
|
||||
"projectRoot": "apps/cart-e2e",
|
||||
"overrides": {}
|
||||
},
|
||||
"products:build": {
|
||||
"id": "products:build",
|
||||
"target": {
|
||||
"project": "products",
|
||||
"target": "build"
|
||||
},
|
||||
"projectRoot": "apps/products",
|
||||
"overrides": {}
|
||||
},
|
||||
"products:build:production": {
|
||||
"id": "products:build:production",
|
||||
"target": {
|
||||
"project": "products",
|
||||
"target": "build",
|
||||
"configuration": "production"
|
||||
},
|
||||
"projectRoot": "apps/products",
|
||||
"overrides": {}
|
||||
},
|
||||
"products:serve": {
|
||||
"id": "products:serve",
|
||||
"target": {
|
||||
"project": "products",
|
||||
"target": "serve"
|
||||
},
|
||||
"projectRoot": "apps/products",
|
||||
"overrides": {}
|
||||
},
|
||||
"products:serve:production": {
|
||||
"id": "products:serve:production",
|
||||
"target": {
|
||||
"project": "products",
|
||||
"target": "serve",
|
||||
"configuration": "production"
|
||||
},
|
||||
"projectRoot": "apps/products",
|
||||
"overrides": {}
|
||||
},
|
||||
"products:extract-i18n": {
|
||||
"id": "products:extract-i18n",
|
||||
"target": {
|
||||
"project": "products",
|
||||
"target": "extract-i18n"
|
||||
},
|
||||
"projectRoot": "apps/products",
|
||||
"overrides": {}
|
||||
},
|
||||
"products:lint": {
|
||||
"id": "products:lint",
|
||||
"target": {
|
||||
"project": "products",
|
||||
"target": "lint"
|
||||
},
|
||||
"projectRoot": "apps/products",
|
||||
"overrides": {}
|
||||
},
|
||||
"products:test": {
|
||||
"id": "products:test",
|
||||
"target": {
|
||||
"project": "products",
|
||||
"target": "test"
|
||||
},
|
||||
"projectRoot": "apps/products",
|
||||
"overrides": {}
|
||||
},
|
||||
"products:deploy": {
|
||||
"id": "products:deploy",
|
||||
"target": {
|
||||
"project": "products",
|
||||
"target": "deploy"
|
||||
},
|
||||
"projectRoot": "apps/products",
|
||||
"overrides": {}
|
||||
},
|
||||
"cart:build:production": {
|
||||
"id": "cart:build:production",
|
||||
"target": {
|
||||
"project": "cart",
|
||||
"target": "build",
|
||||
"configuration": "production"
|
||||
},
|
||||
"projectRoot": "apps/cart",
|
||||
"overrides": {}
|
||||
},
|
||||
"cart:build:development": {
|
||||
"id": "cart:build:development",
|
||||
"target": {
|
||||
"project": "cart",
|
||||
"target": "build",
|
||||
"configuration": "development"
|
||||
},
|
||||
"projectRoot": "apps/cart",
|
||||
"overrides": {}
|
||||
},
|
||||
"cart:serve:development": {
|
||||
"id": "cart:serve:development",
|
||||
"target": {
|
||||
"project": "cart",
|
||||
"target": "serve",
|
||||
"configuration": "development"
|
||||
},
|
||||
"projectRoot": "apps/cart",
|
||||
"overrides": {}
|
||||
},
|
||||
"cart:serve:production": {
|
||||
"id": "cart:serve:production",
|
||||
"target": {
|
||||
"project": "cart",
|
||||
"target": "serve",
|
||||
"configuration": "production"
|
||||
},
|
||||
"projectRoot": "apps/cart",
|
||||
"overrides": {}
|
||||
},
|
||||
"cart:lint": {
|
||||
"id": "cart:lint",
|
||||
"target": {
|
||||
"project": "cart",
|
||||
"target": "lint"
|
||||
},
|
||||
"projectRoot": "apps/cart",
|
||||
"overrides": {}
|
||||
},
|
||||
"cart:test": {
|
||||
"id": "cart:test",
|
||||
"target": {
|
||||
"project": "cart",
|
||||
"target": "test"
|
||||
},
|
||||
"projectRoot": "apps/cart",
|
||||
"overrides": {}
|
||||
},
|
||||
"cart:deploy": {
|
||||
"id": "cart:deploy",
|
||||
"target": {
|
||||
"project": "cart",
|
||||
"target": "deploy"
|
||||
},
|
||||
"projectRoot": "apps/cart",
|
||||
"overrides": {}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"products-product-detail-page:lint": [],
|
||||
"products-product-detail-page:test": [],
|
||||
"shared-product-state:lint": [],
|
||||
"shared-product-state:test": [],
|
||||
"shared-product-types:lint": [],
|
||||
"shared-product-data:lint": [],
|
||||
"products-home-page:lint": [],
|
||||
"products-home-page:test": [],
|
||||
"shared-cart-state:lint": [],
|
||||
"shared-cart-state:test": [],
|
||||
"shared-product-ui:lint": [],
|
||||
"shared-product-ui:test": [],
|
||||
"shared-e2e-utils:lint": [],
|
||||
"cart-cart-page:lint": [],
|
||||
"cart-cart-page:test": [],
|
||||
"shared-header:lint": [],
|
||||
"shared-header:test": [],
|
||||
"shared-jsxify:lint": [],
|
||||
"products-e2e:e2e": [],
|
||||
"products-e2e:e2e:production": [],
|
||||
"products-e2e:lint": [],
|
||||
"cart-e2e:e2e": [],
|
||||
"cart-e2e:e2e:production": [],
|
||||
"cart-e2e:lint": [],
|
||||
"products:build": [],
|
||||
"products:build:production": [],
|
||||
"products:serve": [],
|
||||
"products:serve:production": [],
|
||||
"products:extract-i18n": [],
|
||||
"products:lint": [],
|
||||
"products:test": [],
|
||||
"products:deploy": [],
|
||||
"cart:build:production": [],
|
||||
"cart:build:development": [],
|
||||
"cart:serve:development": [],
|
||||
"cart:serve:production": [],
|
||||
"cart:lint": [],
|
||||
"cart:test": [],
|
||||
"cart:deploy": []
|
||||
},
|
||||
"continuousDependencies": {},
|
||||
"roots": [
|
||||
"products-product-detail-page:lint",
|
||||
"products-product-detail-page:test",
|
||||
"shared-product-state:lint",
|
||||
"shared-product-state:test",
|
||||
"shared-product-types:lint",
|
||||
"shared-product-data:lint",
|
||||
"products-home-page:lint",
|
||||
"products-home-page:test",
|
||||
"shared-cart-state:lint",
|
||||
"shared-cart-state:test",
|
||||
"shared-product-ui:lint",
|
||||
"shared-product-ui:test",
|
||||
"shared-e2e-utils:lint",
|
||||
"cart-cart-page:lint",
|
||||
"cart-cart-page:test",
|
||||
"shared-header:lint",
|
||||
"shared-header:test",
|
||||
"shared-jsxify:lint",
|
||||
"products-e2e:e2e",
|
||||
"products-e2e:e2e:production",
|
||||
"products-e2e:lint",
|
||||
"cart-e2e:e2e",
|
||||
"cart-e2e:e2e:production",
|
||||
"cart-e2e:lint",
|
||||
"products:build",
|
||||
"products:build:production",
|
||||
"products:serve",
|
||||
"products:serve:production",
|
||||
"products:extract-i18n",
|
||||
"products:lint",
|
||||
"products:test",
|
||||
"products:deploy",
|
||||
"cart:build:production",
|
||||
"cart:build:development",
|
||||
"cart:serve:development",
|
||||
"cart:serve:production",
|
||||
"cart:lint",
|
||||
"cart:test",
|
||||
"cart:deploy"
|
||||
]
|
||||
},
|
||||
"plans": {},
|
||||
"error": null
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,18 @@
|
||||
window.exclude = [];
|
||||
window.watch = true;
|
||||
window.environment = 'watch';
|
||||
window.useXstateInspect = false;
|
||||
|
||||
window.appConfig = {
|
||||
showDebugger: false,
|
||||
showExperimentalFeatures: true,
|
||||
workspaces: [
|
||||
{
|
||||
id: 'local',
|
||||
label: 'local',
|
||||
projectGraphUrl: 'assets/project-graphs/e2e.json',
|
||||
taskGraphUrl: 'assets/task-graphs/e2e.json',
|
||||
},
|
||||
],
|
||||
defaultWorkspaceId: 'local',
|
||||
};
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
Vendored
+63
@@ -0,0 +1,63 @@
|
||||
// nx-ignore-next-line
|
||||
import type {
|
||||
ExpandedTaskInputsReponse,
|
||||
ProjectGraphClientResponse,
|
||||
TaskGraphClientResponse,
|
||||
} from 'nx/src/command-line/graph/graph';
|
||||
import type { AppConfig, ExternalApi } from '@nx/graph/shared';
|
||||
import {
|
||||
ProjectDetailsEvents,
|
||||
projectDetailsMachine,
|
||||
ProjectDetailsState,
|
||||
} from './app/console/project-details/project-details.machine';
|
||||
import { Interpreter } from 'xstate';
|
||||
|
||||
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;
|
||||
|
||||
// using bundled graph components directly from outside the graph app
|
||||
__NX_RENDER_GRAPH__?: boolean;
|
||||
renderPDV?: (
|
||||
data: any
|
||||
) => Interpreter<ProjectDetailsState, any, ProjectDetailsEvents>;
|
||||
renderError?: (data: any) => void;
|
||||
renderMigrate?: (
|
||||
data: any
|
||||
) => Interpreter<MigrateState, any, MigrateEvents>;
|
||||
}
|
||||
}
|
||||
declare module 'cytoscape' {
|
||||
interface Core {
|
||||
anywherePanning: Function;
|
||||
}
|
||||
|
||||
interface ElementDefinition {
|
||||
pannable?: boolean;
|
||||
}
|
||||
|
||||
interface NodeSingular {
|
||||
popperRef: Function;
|
||||
pannable: () => boolean;
|
||||
}
|
||||
|
||||
interface EdgeSingular {
|
||||
popperRef: Function;
|
||||
}
|
||||
|
||||
namespace Css {
|
||||
interface EdgeLine {
|
||||
'edge-text-rotation'?: string;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Nx Workspace Project Graph</title>
|
||||
<base href="/" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
||||
|
||||
<script id="environment" src="environment.js"></script>
|
||||
|
||||
<!-- Theming -->
|
||||
<script>
|
||||
// On page load or when changing themes, best to add inline in `head` to avoid FOUC
|
||||
if (
|
||||
localStorage.getItem('nx-dep-graph-theme') === 'dark' ||
|
||||
(localStorage.getItem('nx-dep-graph-theme') === null &&
|
||||
window.matchMedia('(prefers-color-scheme: dark)').matches)
|
||||
) {
|
||||
document.documentElement.classList.add('dark');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body
|
||||
class="h-screen w-screen bg-white text-slate-500 dark:bg-slate-900 dark:text-slate-400"
|
||||
>
|
||||
<div class="flex h-full w-full p-0" id="app"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,122 @@
|
||||
/* eslint-disable import/first */
|
||||
// debug must be first import
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
require('preact/debug');
|
||||
}
|
||||
|
||||
import { projectDetailsMachine } from './app/console-project-details/project-details.machine';
|
||||
|
||||
// nx-ignore-next-line
|
||||
import type { ProjectGraphProjectNode } from '@nx/devkit';
|
||||
// nx-ignore-next-line
|
||||
import type { GraphError } from 'nx/src/command-line/graph/graph';
|
||||
// nx-ignore-next-line
|
||||
import { MigrationsJsonMetadata } from 'nx/src/command-line/migrate/migrate-ui-api';
|
||||
// nx-ignore-next-line
|
||||
import { GeneratedMigrationDetails } from 'nx/src/config/misc-interfaces';
|
||||
|
||||
import { StrictMode } from 'react';
|
||||
import { inspect } from '@xstate/inspect';
|
||||
import { App } from './app/app';
|
||||
import { ExternalApiImpl } from './app/external-api-impl';
|
||||
import { render } from 'preact';
|
||||
import { ErrorPage } from './app/ui-components/error-page';
|
||||
import { ProjectDetailsApp } from './app/console-project-details/project-details.app';
|
||||
import { interpret } from 'xstate';
|
||||
import { MigrateApp } from './app/console-migrate/migrate.app';
|
||||
import { migrateMachine } from './app/console-migrate/migrate.machine';
|
||||
import { ThemeProvider } from '@nx/graph-internal-ui-render-config';
|
||||
|
||||
if (window.__NX_RENDER_GRAPH__ === false) {
|
||||
window.externalApi = new ExternalApiImpl();
|
||||
|
||||
window.renderPDV = (data: {
|
||||
project: ProjectGraphProjectNode;
|
||||
sourceMap: Record<string, string[]>;
|
||||
connectedToCloud: boolean;
|
||||
errors?: GraphError[];
|
||||
}) => {
|
||||
const service = interpret(projectDetailsMachine).start();
|
||||
|
||||
service.send({
|
||||
type: 'loadData',
|
||||
...data,
|
||||
});
|
||||
|
||||
render(
|
||||
<StrictMode>
|
||||
<ThemeProvider>
|
||||
<ProjectDetailsApp service={service} />
|
||||
</ThemeProvider>
|
||||
</StrictMode>,
|
||||
document.getElementById('app')
|
||||
);
|
||||
|
||||
return service;
|
||||
};
|
||||
|
||||
window.renderError = (data: {
|
||||
message: string;
|
||||
stack?: string;
|
||||
errors: GraphError[];
|
||||
}) => {
|
||||
render(
|
||||
<StrictMode>
|
||||
<ThemeProvider>
|
||||
<ErrorPage {...data} />
|
||||
</ThemeProvider>
|
||||
</StrictMode>,
|
||||
document.getElementById('app')
|
||||
);
|
||||
};
|
||||
|
||||
window.renderMigrate = (data: {
|
||||
migrations: GeneratedMigrationDetails[];
|
||||
'nx-console': MigrationsJsonMetadata;
|
||||
}) => {
|
||||
const service = interpret(migrateMachine).start();
|
||||
|
||||
service.send({
|
||||
type: 'loadData',
|
||||
...data,
|
||||
});
|
||||
|
||||
render(
|
||||
<StrictMode>
|
||||
<ThemeProvider>
|
||||
<MigrateApp service={service} />
|
||||
</ThemeProvider>
|
||||
</StrictMode>,
|
||||
document.getElementById('app')
|
||||
);
|
||||
|
||||
return service;
|
||||
};
|
||||
} else {
|
||||
if (window.useXstateInspect === true) {
|
||||
inspect({
|
||||
url: 'https://stately.ai/viz?inspect',
|
||||
iframe: false, // open in new window
|
||||
});
|
||||
}
|
||||
|
||||
window.externalApi = new ExternalApiImpl();
|
||||
const container = document.getElementById('app');
|
||||
|
||||
if (!window.appConfig) {
|
||||
render(
|
||||
<p>
|
||||
No environment could be found. Please run{' '}
|
||||
<pre>npx nx run graph-client:generate-dev-environment-js</pre>.
|
||||
</p>,
|
||||
container
|
||||
);
|
||||
} else {
|
||||
render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
container
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*! purgecss start ignore */
|
||||
@import './syntax-highlight.css';
|
||||
/*! purgecss end ignore */
|
||||
|
||||
@import 'tailwindcss';
|
||||
|
||||
@plugin '@tailwindcss/typography';
|
||||
@plugin '@tailwindcss/forms' {
|
||||
strategy: class;
|
||||
}
|
||||
|
||||
@custom-variant dark (&:where(.dark, .dark *));
|
||||
|
||||
@source '../src';
|
||||
@source '../../ui-code-block/src';
|
||||
@source '../../ui-common/src';
|
||||
@source '../../ui-icons/src';
|
||||
@source '../../ui-project-details/src';
|
||||
@source '../../ui-render-config/src';
|
||||
@source '../../shared/src';
|
||||
@source '../../migrate/src';
|
||||
@source '../../../node_modules/@nx/graph';
|
||||
|
||||
@source not '../../**/*.{spec,test,stories}.*';
|
||||
@source not '../../**/eslint.config.*';
|
||||
|
||||
/* v3 compat: default border color was gray-200, v4 is currentColor. */
|
||||
@layer base {
|
||||
*,
|
||||
::after,
|
||||
::before,
|
||||
::backdrop,
|
||||
::file-selector-button {
|
||||
border-color: var(--color-gray-200, currentColor);
|
||||
}
|
||||
}
|
||||
|
||||
/* Strip default quote characters around inline code and blockquote. */
|
||||
.prose :where(code):not(:where([class~='not-prose'] *))::before,
|
||||
.prose :where(code):not(:where([class~='not-prose'] *))::after,
|
||||
.prose
|
||||
:where(blockquote p:first-of-type):not(
|
||||
:where([class~='not-prose'] *)
|
||||
)::before,
|
||||
.prose
|
||||
:where(blockquote p:last-of-type):not(:where([class~='not-prose'] *))::after {
|
||||
content: '';
|
||||
}
|
||||
|
||||
/** Scrollbars **/
|
||||
.dark {
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tooltips
|
||||
*/
|
||||
[data-tooltip] {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
[data-tooltip]:before {
|
||||
background-color: hsla(214, 61%, 11%, 1);
|
||||
border-radius: 0.375rem;
|
||||
color: hsla(0, 0%, 95%, 1);
|
||||
content: attr(data-tooltip);
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
padding: 0.25rem 0.5rem;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
text-transform: capitalize;
|
||||
top: 120%;
|
||||
transition: opacity 150ms ease-out;
|
||||
visibility: hidden;
|
||||
z-index: 1;
|
||||
width: max-content;
|
||||
}
|
||||
[data-tooltip][data-tooltip-align-right]:before {
|
||||
left: auto;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
[data-tooltip]:hover:before {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
/* Dark mode */
|
||||
html.dark .adaptive-icon {
|
||||
/* fill: white; */
|
||||
filter: invert(1);
|
||||
}
|
||||
|
||||
.adaptive-icon {
|
||||
fill: black;
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
html {
|
||||
--interactive-muted: #94a3b8;
|
||||
}
|
||||
.dark {
|
||||
--interactive-muted: #64748b;
|
||||
}
|
||||
|
||||
.hljs .hljs-comment,
|
||||
.hljs .hljs-quote {
|
||||
color: var(--interactive-muted);
|
||||
}
|
||||
|
||||
.hljs .hljs-addition,
|
||||
.hljs .hljs-keyword,
|
||||
.hljs .hljs-selector-tag {
|
||||
color: #859900;
|
||||
}
|
||||
|
||||
.hljs .hljs-doctag,
|
||||
.hljs .hljs-literal,
|
||||
.hljs .hljs-meta .hljs .hljs-meta-string,
|
||||
.hljs .hljs-number,
|
||||
.hljs .hljs-regexp,
|
||||
.hljs .hljs-string {
|
||||
color: #2aa198;
|
||||
}
|
||||
|
||||
.hljs .hljs-name,
|
||||
.hljs .hljs-section,
|
||||
.hljs .hljs-selector-class,
|
||||
.hljs .hljs-selector-id,
|
||||
.hljs .hljs-title {
|
||||
color: #268bd2;
|
||||
}
|
||||
|
||||
.hljs .hljs-attr,
|
||||
.hljs .hljs-attribute,
|
||||
.hljs .hljs-class .hljs .hljs-title,
|
||||
.hljs .hljs-template-variable,
|
||||
.hljs .hljs-type,
|
||||
.hljs .hljs-variable {
|
||||
color: #b58900;
|
||||
}
|
||||
|
||||
.hljs .hljs-bullet,
|
||||
.hljs .hljs-link,
|
||||
.hljs .hljs-meta,
|
||||
.hljs .hljs-meta .hljs .hljs-keyword,
|
||||
.hljs .hljs-selector-attr,
|
||||
.hljs .hljs-selector-pseudo,
|
||||
.hljs .hljs-subst,
|
||||
.hljs .hljs-symbol {
|
||||
color: #cb4b16;
|
||||
}
|
||||
|
||||
.hljs .hljs-built_in,
|
||||
.hljs .hljs-deletion {
|
||||
color: #dc322f;
|
||||
}
|
||||
|
||||
.hljs .hljs-formula {
|
||||
background: #073642;
|
||||
}
|
||||
|
||||
.hljs .hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.hljs .hljs-strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.linenumber {
|
||||
border-color: rgb(59 130 246);
|
||||
background: #e5e7eb;
|
||||
}
|
||||
html.dark .linenumber {
|
||||
background: #e5e7eb33;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "out-tsc",
|
||||
"tsBuildInfoFile": "out-tsc/tsconfig.app.tsbuildinfo",
|
||||
"declaration": true,
|
||||
"composite": true,
|
||||
"types": ["node"],
|
||||
"lib": ["DOM", "es2022"]
|
||||
},
|
||||
"files": [
|
||||
"../../node_modules/@nx/react/typings/cssmodule.d.ts",
|
||||
"../../node_modules/@nx/react/typings/image.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"out-tsc",
|
||||
"**/*.spec.ts",
|
||||
"**/*.test.ts",
|
||||
"**/*.spec.tsx",
|
||||
"**/*.test.tsx",
|
||||
"**/*.spec.js",
|
||||
"**/*.test.js",
|
||||
"**/*.spec.jsx",
|
||||
"**/*.test.jsx",
|
||||
"jest.config.ts",
|
||||
"**/*.stories.ts",
|
||||
"**/*.stories.js",
|
||||
"**/*.stories.jsx",
|
||||
"**/*.stories.tsx"
|
||||
],
|
||||
"include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"],
|
||||
"references": [
|
||||
{
|
||||
"path": "../ui-code-block/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../ui-common/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../shared/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../migrate/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../ui-project-details/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../project-details/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../ui-render-config/tsconfig.lib.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "out-tsc/spec",
|
||||
"customConditions": ["@nx/nx-source"],
|
||||
"types": ["jest", "node"],
|
||||
"lib": ["DOM"]
|
||||
},
|
||||
"exclude": ["out-tsc"],
|
||||
"include": [
|
||||
"**/*.test.ts",
|
||||
"**/*.spec.ts",
|
||||
"**/*.test.tsx",
|
||||
"**/*.spec.tsx",
|
||||
"**/*.test.js",
|
||||
"**/*.spec.js",
|
||||
"**/*.test.jsx",
|
||||
"**/*.spec.jsx",
|
||||
"**/*.d.ts",
|
||||
"jest.config.ts"
|
||||
],
|
||||
"files": [
|
||||
"../../node_modules/@nx/react/typings/cssmodule.d.ts",
|
||||
"../../node_modules/@nx/react/typings/image.d.ts"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
const { join } = require('node:path');
|
||||
// nx-ignore-next-line
|
||||
const { NxAppWebpackPlugin } = require('@nx/webpack/app-plugin');
|
||||
// nx-ignore-next-line
|
||||
const { NxReactWebpackPlugin } = require('@nx/react/webpack-plugin');
|
||||
|
||||
module.exports = {
|
||||
output: {
|
||||
path: join(__dirname, 'dist'),
|
||||
},
|
||||
devServer: {
|
||||
port: getPort(process.env.NX_TASK_TARGET_CONFIGURATION),
|
||||
historyApiFallback: {
|
||||
disableDotRule: true,
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
react: 'preact/compat',
|
||||
'react-dom/test-utils': 'preact/test-utils',
|
||||
'react-dom': 'preact/compat', // Must be below test-utils
|
||||
'react/jsx-runtime': 'preact/jsx-runtime',
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
new NxAppWebpackPlugin({
|
||||
index: './src/index.html',
|
||||
compiler: 'babel',
|
||||
main: './src/main.tsx',
|
||||
tsConfig: './tsconfig.app.json',
|
||||
styles: ['./src/styles.css'],
|
||||
scripts: [],
|
||||
assets: getAssets(process.env.NX_TASK_TARGET_CONFIGURATION),
|
||||
webpackConfig: './webpack.config.js',
|
||||
outputHashing: 'none',
|
||||
deleteOutputPath: false,
|
||||
}),
|
||||
new NxReactWebpackPlugin({
|
||||
svgr: false,
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
function getPort(nxConfiguration) {
|
||||
switch (nxConfiguration) {
|
||||
case 'nx-console':
|
||||
return 4202;
|
||||
case 'release':
|
||||
return 4203;
|
||||
case 'watch':
|
||||
return 4204;
|
||||
case 'release-static':
|
||||
return 4205;
|
||||
case 'dev-e2e':
|
||||
return 4206;
|
||||
default: // dev
|
||||
return 4201;
|
||||
}
|
||||
}
|
||||
|
||||
function getAssets(nxConfiguration) {
|
||||
switch (nxConfiguration) {
|
||||
case 'nx-console':
|
||||
return [
|
||||
'./src/favicon.ico',
|
||||
{
|
||||
input: './src/assets/project-graphs',
|
||||
output: '/assets/project-graphs',
|
||||
glob: 'e2e.json',
|
||||
},
|
||||
{
|
||||
input: './src/assets/nx-console',
|
||||
output: '/',
|
||||
glob: 'environment.js',
|
||||
},
|
||||
];
|
||||
case 'release':
|
||||
return [
|
||||
'./src/favicon.ico',
|
||||
{
|
||||
input: './src/assets/project-graphs',
|
||||
output: '/assets/project-graphs',
|
||||
glob: 'e2e.json',
|
||||
},
|
||||
{
|
||||
input: './src/assets/task-graphs',
|
||||
output: '/assets/task-graphs',
|
||||
glob: 'e2e.json',
|
||||
},
|
||||
{
|
||||
input: './src/assets/source-maps',
|
||||
output: '/assets/source-maps',
|
||||
glob: 'e2e.json',
|
||||
},
|
||||
{
|
||||
input: './src/assets/release',
|
||||
output: '/',
|
||||
glob: 'environment.js',
|
||||
},
|
||||
];
|
||||
case 'watch':
|
||||
return [
|
||||
'./src/favicon.ico',
|
||||
{
|
||||
input: './src/assets/watch',
|
||||
output: '/',
|
||||
glob: 'environment.js',
|
||||
},
|
||||
];
|
||||
case 'release-static':
|
||||
return [
|
||||
'./src/favicon.ico',
|
||||
{
|
||||
input: './src/assets/project-graphs',
|
||||
output: '/assets/project-graphs',
|
||||
glob: 'e2e.json',
|
||||
},
|
||||
{
|
||||
input: './src/assets/task-graphs',
|
||||
output: '/assets/task-graphs',
|
||||
glob: 'e2e.json',
|
||||
},
|
||||
{
|
||||
input: './src/assets/release-static',
|
||||
output: '/',
|
||||
glob: 'environment.js',
|
||||
},
|
||||
];
|
||||
case 'dev-e2e':
|
||||
return [
|
||||
'./src/favicon.ico',
|
||||
'./src/assets/project-graphs/',
|
||||
'./src/assets/task-graphs/',
|
||||
'./src/assets/task-inputs/',
|
||||
'./src/assets/source-maps/',
|
||||
{
|
||||
input: './src/assets/dev-e2e',
|
||||
output: '/',
|
||||
glob: 'environment.js',
|
||||
},
|
||||
];
|
||||
default: // dev
|
||||
return [
|
||||
'./src/favicon.ico',
|
||||
'./src/assets/project-graphs/',
|
||||
'./src/assets/task-graphs/',
|
||||
'./src/assets/generated-project-graphs/',
|
||||
'./src/assets/generated-task-graphs/',
|
||||
'./src/assets/generated-task-inputs/',
|
||||
'./src/assets/generated-source-maps/',
|
||||
{
|
||||
input: './src/assets/dev',
|
||||
output: '/',
|
||||
glob: 'environment.js',
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user