chore(graph): provide task data to graph app (#13058)

This commit is contained in:
Philip Fulcher 2022-11-09 08:40:17 -07:00 committed by GitHub
parent c842535fa1
commit 7557bd4f48
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 121 additions and 9 deletions

View File

@ -589,7 +589,7 @@ describe('Nx Affected and Graph Tests', () => {
const environmentJs = readFile('static/environment.js'); const environmentJs = readFile('static/environment.js');
const affectedProjects = environmentJs const affectedProjects = environmentJs
.match(/"affected":\[(.*)\],/)[1] .match(/"affected":\[(.*?)\]/)[1]
?.split(','); ?.split(',');
expect(affectedProjects).toContain(`"${myapp}"`); expect(affectedProjects).toContain(`"${myapp}"`);

View File

@ -10,7 +10,7 @@ window.appConfig = {
{ {
id: 'local', id: 'local',
label: 'local', label: 'local',
url: 'projectGraph.json', url: 'project-graph.json',
}, },
], ],
defaultProjectGraph: 'local', defaultProjectGraph: 'local',

View File

@ -9,7 +9,7 @@ import * as open from 'open';
import { basename, dirname, extname, isAbsolute, join, parse } from 'path'; import { basename, dirname, extname, isAbsolute, join, parse } from 'path';
import { performance } from 'perf_hooks'; import { performance } from 'perf_hooks';
import { URL, URLSearchParams } from 'url'; import { URL, URLSearchParams } from 'url';
import { workspaceLayout } from '../config/configuration'; import { readNxJson, workspaceLayout } from '../config/configuration';
import { defaultFileHasher } from '../hasher/file-hasher'; import { defaultFileHasher } from '../hasher/file-hasher';
import { output } from '../utils/output'; import { output } from '../utils/output';
import { writeJsonFile } from '../utils/fileutils'; import { writeJsonFile } from '../utils/fileutils';
@ -21,6 +21,9 @@ import {
} from '../config/project-graph'; } from '../config/project-graph';
import { pruneExternalNodes } from '../project-graph/operators'; import { pruneExternalNodes } from '../project-graph/operators';
import { createProjectGraphAsync } from '../project-graph/project-graph'; import { createProjectGraphAsync } from '../project-graph/project-graph';
import { createTaskGraph } from 'nx/src/tasks-runner/create-task-graph';
import { TargetDefaults, TargetDependencies } from 'nx/src/config/nx-json';
import { TaskGraph } from 'nx/src/config/task-graph';
export interface DepGraphClientResponse { export interface DepGraphClientResponse {
hash: string; hash: string;
@ -33,6 +36,14 @@ export interface DepGraphClientResponse {
exclude: string[]; exclude: string[];
} }
export type TaskGraphDependencies = Record<
string,
Record<string, Record<string, TaskGraph>>
>;
export interface TaskGraphClientResponse {
dependencies: TaskGraphDependencies;
}
// maps file extention to MIME types // maps file extention to MIME types
const mimeType = { const mimeType = {
'.ico': 'image/x-icon', '.ico': 'image/x-icon',
@ -55,7 +66,8 @@ function buildEnvironmentJs(
exclude: string[], exclude: string[],
watchMode: boolean, watchMode: boolean,
localMode: 'build' | 'serve', localMode: 'build' | 'serve',
depGraphClientResponse?: DepGraphClientResponse depGraphClientResponse?: DepGraphClientResponse,
taskGraphClientResponse?: TaskGraphClientResponse
) { ) {
let environmentJs = `window.exclude = ${JSON.stringify(exclude)}; let environmentJs = `window.exclude = ${JSON.stringify(exclude)};
window.watch = ${!!watchMode}; window.watch = ${!!watchMode};
@ -69,7 +81,7 @@ function buildEnvironmentJs(
{ {
id: 'local', id: 'local',
label: 'local', label: 'local',
url: 'projectGraph.json', url: 'project-graph.json',
} }
], ],
defaultProjectGraph: 'local', defaultProjectGraph: 'local',
@ -79,9 +91,16 @@ function buildEnvironmentJs(
if (localMode === 'build') { if (localMode === 'build') {
environmentJs += `window.projectGraphResponse = ${JSON.stringify( environmentJs += `window.projectGraphResponse = ${JSON.stringify(
depGraphClientResponse depGraphClientResponse
)};`; )};
`;
environmentJs += `window.taskGraphResponse = ${JSON.stringify(
taskGraphClientResponse
)};
`;
} else { } else {
environmentJs += `window.projectGraphResponse = null;`; environmentJs += `window.projectGraphResponse = null;`;
environmentJs += `window.taskGraphResponse = null;`;
} }
return environmentJs; return environmentJs;
@ -234,11 +253,14 @@ export async function generateGraph(
affectedProjects affectedProjects
); );
const taskGraphClientResponse = await createTaskGraphClientResponse();
const environmentJs = buildEnvironmentJs( const environmentJs = buildEnvironmentJs(
args.exclude || [], args.exclude || [],
args.watch, args.watch,
!!args.file && args.file.endsWith('html') ? 'build' : 'serve', !!args.file && args.file.endsWith('html') ? 'build' : 'serve',
depGraphClientResponse depGraphClientResponse,
taskGraphClientResponse
); );
html = html.replace(/src="/g, 'src="static/'); html = html.replace(/src="/g, 'src="static/');
html = html.replace(/href="styles/g, 'href="static/styles'); html = html.replace(/href="styles/g, 'href="static/styles');
@ -316,7 +338,7 @@ async function startServer(
currentDepGraphClientResponse.groupByFolder = groupByFolder; currentDepGraphClientResponse.groupByFolder = groupByFolder;
currentDepGraphClientResponse.exclude = exclude; currentDepGraphClientResponse.exclude = exclude;
const app = http.createServer((req, res) => { const app = http.createServer(async (req, res) => {
// parse URL // parse URL
const parsedUrl = new URL(req.url, `http://${host}:${port}`); const parsedUrl = new URL(req.url, `http://${host}:${port}`);
// extract URL path // extract URL path
@ -326,12 +348,18 @@ async function startServer(
const sanitizePath = basename(parsedUrl.pathname); const sanitizePath = basename(parsedUrl.pathname);
if (sanitizePath === 'projectGraph.json') { if (sanitizePath === 'project-graph.json') {
res.writeHead(200, { 'Content-Type': 'application/json' }); res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(currentDepGraphClientResponse)); res.end(JSON.stringify(currentDepGraphClientResponse));
return; return;
} }
if (sanitizePath === 'task-graph.json') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(await createTaskGraphClientResponse()));
return;
}
if (sanitizePath === 'currentHash') { if (sanitizePath === 'currentHash') {
res.writeHead(200, { 'Content-Type': 'application/json' }); res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ hash: currentDepGraphClientResponse.hash })); res.end(JSON.stringify({ hash: currentDepGraphClientResponse.hash }));
@ -486,6 +514,7 @@ async function createDepGraphClientResponse(
tags: project.data.tags, tags: project.data.tags,
root: project.data.root, root: project.data.root,
files: project.data.files, files: project.data.files,
targets: project.data.targets,
}, },
} as ProjectGraphProjectNode) } as ProjectGraphProjectNode)
); );
@ -520,3 +549,86 @@ async function createDepGraphClientResponse(
affected, affected,
}; };
} }
async function createTaskGraphClientResponse(): Promise<TaskGraphClientResponse> {
let graph = pruneExternalNodes(
await createProjectGraphAsync({ exitOnError: true })
);
performance.mark('task graph generation:start');
const tasks = getAllTaskGraphsForWorkspace(graph);
performance.mark('task graph generation:end');
performance.measure(
'task graph generation',
'task graph generation:start',
'task graph generation:end'
);
return {
dependencies: tasks,
};
}
function getAllTaskGraphsForWorkspace(
projectGraph: ProjectGraph
): TaskGraphDependencies {
const nxJson = readNxJson();
const defaultDependencyConfigs = mapTargetDefaultsToDependencies(
nxJson.targetDefaults
);
const taskGraphs: TaskGraphDependencies = {};
for (const projectName in projectGraph.nodes) {
const project = projectGraph.nodes[projectName];
const targets = Object.keys(project.data.targets);
taskGraphs[projectName] = {};
targets.forEach((target) => {
taskGraphs[projectName][target] = {};
const configurations = Object.keys(
project.data.targets[target]?.configurations || {}
);
if (configurations.length > 0) {
configurations.forEach((configuration) => {
taskGraphs[projectName][target][configuration] = createTaskGraph(
projectGraph,
defaultDependencyConfigs,
[projectName],
[target],
configuration,
{}
);
});
} else {
taskGraphs[projectName][target]['no-configurations'] = createTaskGraph(
projectGraph,
defaultDependencyConfigs,
[projectName],
[target],
undefined,
{}
);
}
});
}
return taskGraphs;
}
function mapTargetDefaultsToDependencies(
defaults: TargetDefaults
): TargetDependencies {
const res = {};
Object.keys(defaults).forEach((k) => {
res[k] = defaults[k].dependsOn;
});
return res;
}