@rushstack/rush-buildxl-graph-plugin 5.167.0 → 5.169.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{lib → lib-dts}/tsdoc-metadata.json +1 -1
- package/lib-esm/DropBuildGraphPlugin.js +62 -0
- package/lib-esm/DropBuildGraphPlugin.js.map +1 -0
- package/lib-esm/GraphProcessor.js +141 -0
- package/lib-esm/GraphProcessor.js.map +1 -0
- package/lib-esm/debugGraphFiltering.js +92 -0
- package/lib-esm/debugGraphFiltering.js.map +1 -0
- package/lib-esm/dropGraph.js +50 -0
- package/lib-esm/dropGraph.js.map +1 -0
- package/lib-esm/index.js +5 -0
- package/lib-esm/index.js.map +1 -0
- package/lib-esm/schemas/rush-buildxl-graph-plugin.schema.json +15 -0
- package/package.json +33 -9
- package/rush-plugin-manifest.json +2 -2
- /package/{lib → lib-commonjs}/DropBuildGraphPlugin.js +0 -0
- /package/{lib → lib-commonjs}/DropBuildGraphPlugin.js.map +0 -0
- /package/{lib → lib-commonjs}/GraphProcessor.js +0 -0
- /package/{lib → lib-commonjs}/GraphProcessor.js.map +0 -0
- /package/{lib → lib-commonjs}/debugGraphFiltering.js +0 -0
- /package/{lib → lib-commonjs}/debugGraphFiltering.js.map +0 -0
- /package/{lib → lib-commonjs}/dropGraph.js +0 -0
- /package/{lib → lib-commonjs}/dropGraph.js.map +0 -0
- /package/{lib → lib-commonjs}/index.js +0 -0
- /package/{lib → lib-commonjs}/index.js.map +0 -0
- /package/{lib → lib-commonjs}/schemas/rush-buildxl-graph-plugin.schema.json +0 -0
- /package/{lib → lib-dts}/DropBuildGraphPlugin.d.ts +0 -0
- /package/{lib → lib-dts}/DropBuildGraphPlugin.d.ts.map +0 -0
- /package/{lib → lib-dts}/GraphProcessor.d.ts +0 -0
- /package/{lib → lib-dts}/GraphProcessor.d.ts.map +0 -0
- /package/{lib → lib-dts}/debugGraphFiltering.d.ts +0 -0
- /package/{lib → lib-dts}/debugGraphFiltering.d.ts.map +0 -0
- /package/{lib → lib-dts}/dropGraph.d.ts +0 -0
- /package/{lib → lib-dts}/dropGraph.d.ts.map +0 -0
- /package/{lib → lib-dts}/index.d.ts +0 -0
- /package/{lib → lib-dts}/index.d.ts.map +0 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
|
|
2
|
+
// See LICENSE in the project root for license information.
|
|
3
|
+
import { RushConstants } from '@rushstack/rush-sdk';
|
|
4
|
+
import { CommandLineParameterKind } from '@rushstack/ts-command-line';
|
|
5
|
+
const PLUGIN_NAME = 'DropBuildGraphPlugin';
|
|
6
|
+
const DROP_GRAPH_PARAMETER_LONG_NAME = '--drop-graph';
|
|
7
|
+
/**
|
|
8
|
+
* This plugin is used to drop the build graph to a file for BuildXL to consume.
|
|
9
|
+
* @public
|
|
10
|
+
*/
|
|
11
|
+
export class DropBuildGraphPlugin {
|
|
12
|
+
constructor(options) {
|
|
13
|
+
this.pluginName = PLUGIN_NAME;
|
|
14
|
+
this._buildXLCommandNames = options.buildXLCommandNames;
|
|
15
|
+
}
|
|
16
|
+
apply(session, rushConfiguration) {
|
|
17
|
+
async function handleCreateOperationsForCommandAsync(commandName, operations, context) {
|
|
18
|
+
// TODO: Introduce an API to allow plugins to register command line options for arbitrary, existing commands
|
|
19
|
+
// in a repo
|
|
20
|
+
const dropGraphParameter = context.customParameters.get(DROP_GRAPH_PARAMETER_LONG_NAME);
|
|
21
|
+
if (!dropGraphParameter) {
|
|
22
|
+
// TODO: Introduce an API to allow plugins to register command line options for arbitrary, existing commands
|
|
23
|
+
// in a repo
|
|
24
|
+
throw new Error(`The ${DROP_GRAPH_PARAMETER_LONG_NAME} parameter needs to be defined in "${RushConstants.commandLineFilename}" ` +
|
|
25
|
+
`and associated with the action "${commandName}"`);
|
|
26
|
+
}
|
|
27
|
+
else if (dropGraphParameter.kind !== CommandLineParameterKind.String) {
|
|
28
|
+
throw new Error(`The ${DROP_GRAPH_PARAMETER_LONG_NAME} parameter must be a string parameter`);
|
|
29
|
+
}
|
|
30
|
+
const dropGraphPath = dropGraphParameter === null || dropGraphParameter === void 0 ? void 0 : dropGraphParameter.value;
|
|
31
|
+
if (dropGraphPath) {
|
|
32
|
+
const { dropGraphAsync } = await import('./dropGraph');
|
|
33
|
+
const isValid = await dropGraphAsync({
|
|
34
|
+
operations,
|
|
35
|
+
context,
|
|
36
|
+
dropGraphPath,
|
|
37
|
+
rushConfiguration,
|
|
38
|
+
logger: session.getLogger(PLUGIN_NAME)
|
|
39
|
+
});
|
|
40
|
+
if (!isValid) {
|
|
41
|
+
throw new Error('Failed to validate the graph');
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
// If the --drop-graph flag is present, we want to exit the process after dropping the graph
|
|
45
|
+
return new Set();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
return operations;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
for (const buildXLCommandName of this._buildXLCommandNames) {
|
|
53
|
+
session.hooks.runPhasedCommand.for(buildXLCommandName).tap(PLUGIN_NAME, (command) => {
|
|
54
|
+
command.hooks.createOperations.tapPromise({
|
|
55
|
+
name: PLUGIN_NAME,
|
|
56
|
+
stage: Number.MAX_SAFE_INTEGER // Run this after other plugins have created all operations
|
|
57
|
+
}, async (operations, context) => await handleCreateOperationsForCommandAsync(command.actionName, operations, context));
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=DropBuildGraphPlugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DropBuildGraphPlugin.js","sourceRoot":"","sources":["../src/DropBuildGraphPlugin.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,2DAA2D;AAE3D,OAAO,EACL,aAAa,EAOd,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,wBAAwB,EAAmC,MAAM,4BAA4B,CAAC;AAIvG,MAAM,WAAW,GAA2B,sBAAsB,CAAC;AAuBnE,MAAM,8BAA8B,GAAmB,cAAc,CAAC;AAEtE;;;GAGG;AACH,MAAM,OAAO,oBAAoB;IAI/B,YAAmB,OAAgC;QAHnC,eAAU,GAAW,WAAW,CAAC;QAI/C,IAAI,CAAC,oBAAoB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAC1D,CAAC;IAEM,KAAK,CAAC,OAAoB,EAAE,iBAAoC;QACrE,KAAK,UAAU,qCAAqC,CAClD,WAAmB,EACnB,UAA0B,EAC1B,OAAiC;YAEjC,4GAA4G;YAC5G,YAAY;YACZ,MAAM,kBAAkB,GAA2C,OAAO,CAAC,gBAAgB,CAAC,GAAG,CAC7F,8BAA8B,CACD,CAAC;YAChC,IAAI,CAAC,kBAAkB,EAAE,CAAC;gBACxB,4GAA4G;gBAC5G,YAAY;gBACZ,MAAM,IAAI,KAAK,CACb,OAAO,8BAA8B,sCAAsC,aAAa,CAAC,mBAAmB,IAAI;oBAC9G,mCAAmC,WAAW,GAAG,CACpD,CAAC;YACJ,CAAC;iBAAM,IAAI,kBAAkB,CAAC,IAAI,KAAK,wBAAwB,CAAC,MAAM,EAAE,CAAC;gBACvE,MAAM,IAAI,KAAK,CAAC,OAAO,8BAA8B,uCAAuC,CAAC,CAAC;YAChG,CAAC;YAED,MAAM,aAAa,GAAuB,kBAAkB,aAAlB,kBAAkB,uBAAlB,kBAAkB,CAAE,KAAK,CAAC;YACpE,IAAI,aAAa,EAAE,CAAC;gBAClB,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;gBACvD,MAAM,OAAO,GAAY,MAAM,cAAc,CAAC;oBAC5C,UAAU;oBACV,OAAO;oBACP,aAAa;oBACb,iBAAiB;oBACjB,MAAM,EAAE,OAAO,CAAC,SAAS,CAAC,WAAW,CAAC;iBACvC,CAAC,CAAC;gBAEH,IAAI,CAAC,OAAO,EAAE,CAAC;oBACb,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;gBAClD,CAAC;qBAAM,CAAC;oBACN,4FAA4F;oBAC5F,OAAO,IAAI,GAAG,EAAE,CAAC;gBACnB,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,OAAO,UAAU,CAAC;YACpB,CAAC;QACH,CAAC;QAED,KAAK,MAAM,kBAAkB,IAAI,IAAI,CAAC,oBAAoB,EAAE,CAAC;YAC3D,OAAO,CAAC,KAAK,CAAC,gBAAgB,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,OAAuB,EAAE,EAAE;gBAClG,OAAO,CAAC,KAAK,CAAC,gBAAgB,CAAC,UAAU,CACvC;oBACE,IAAI,EAAE,WAAW;oBACjB,KAAK,EAAE,MAAM,CAAC,gBAAgB,CAAC,2DAA2D;iBAC3F,EACD,KAAK,EAAE,UAA0B,EAAE,OAAiC,EAAE,EAAE,CACtE,MAAM,qCAAqC,CAAC,OAAO,CAAC,UAAU,EAAE,UAAU,EAAE,OAAO,CAAC,CACvF,CAAC;YACJ,CAAC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;CACF","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport {\n RushConstants,\n type ICreateOperationsContext,\n type IPhasedCommand,\n type IRushPlugin,\n type Operation,\n type RushConfiguration,\n type RushSession\n} from '@rushstack/rush-sdk';\nimport { CommandLineParameterKind, type CommandLineStringParameter } from '@rushstack/ts-command-line';\n\nimport type { IGraphNode } from './GraphProcessor';\n\nconst PLUGIN_NAME: 'DropBuildGraphPlugin' = 'DropBuildGraphPlugin';\n\n/**\n * This is the type that represents the schema of the drop file\n * @public\n */\nexport interface IBuildXLRushGraph {\n nodes: IGraphNode[];\n repoSettings: {\n commonTempFolder: string;\n };\n}\n\n/**\n * @public\n */\nexport interface IDropGraphPluginOptions {\n /**\n * The names of the commands that will be used to run BuildXL\n */\n buildXLCommandNames: string[];\n}\n\nconst DROP_GRAPH_PARAMETER_LONG_NAME: '--drop-graph' = '--drop-graph';\n\n/**\n * This plugin is used to drop the build graph to a file for BuildXL to consume.\n * @public\n */\nexport class DropBuildGraphPlugin implements IRushPlugin {\n public readonly pluginName: string = PLUGIN_NAME;\n private readonly _buildXLCommandNames: string[];\n\n public constructor(options: IDropGraphPluginOptions) {\n this._buildXLCommandNames = options.buildXLCommandNames;\n }\n\n public apply(session: RushSession, rushConfiguration: RushConfiguration): void {\n async function handleCreateOperationsForCommandAsync(\n commandName: string,\n operations: Set<Operation>,\n context: ICreateOperationsContext\n ): Promise<Set<Operation>> {\n // TODO: Introduce an API to allow plugins to register command line options for arbitrary, existing commands\n // in a repo\n const dropGraphParameter: CommandLineStringParameter | undefined = context.customParameters.get(\n DROP_GRAPH_PARAMETER_LONG_NAME\n ) as CommandLineStringParameter;\n if (!dropGraphParameter) {\n // TODO: Introduce an API to allow plugins to register command line options for arbitrary, existing commands\n // in a repo\n throw new Error(\n `The ${DROP_GRAPH_PARAMETER_LONG_NAME} parameter needs to be defined in \"${RushConstants.commandLineFilename}\" ` +\n `and associated with the action \"${commandName}\"`\n );\n } else if (dropGraphParameter.kind !== CommandLineParameterKind.String) {\n throw new Error(`The ${DROP_GRAPH_PARAMETER_LONG_NAME} parameter must be a string parameter`);\n }\n\n const dropGraphPath: string | undefined = dropGraphParameter?.value;\n if (dropGraphPath) {\n const { dropGraphAsync } = await import('./dropGraph');\n const isValid: boolean = await dropGraphAsync({\n operations,\n context,\n dropGraphPath,\n rushConfiguration,\n logger: session.getLogger(PLUGIN_NAME)\n });\n\n if (!isValid) {\n throw new Error('Failed to validate the graph');\n } else {\n // If the --drop-graph flag is present, we want to exit the process after dropping the graph\n return new Set();\n }\n } else {\n return operations;\n }\n }\n\n for (const buildXLCommandName of this._buildXLCommandNames) {\n session.hooks.runPhasedCommand.for(buildXLCommandName).tap(PLUGIN_NAME, (command: IPhasedCommand) => {\n command.hooks.createOperations.tapPromise(\n {\n name: PLUGIN_NAME,\n stage: Number.MAX_SAFE_INTEGER // Run this after other plugins have created all operations\n },\n async (operations: Set<Operation>, context: ICreateOperationsContext) =>\n await handleCreateOperationsForCommandAsync(command.actionName, operations, context)\n );\n });\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
|
|
2
|
+
// See LICENSE in the project root for license information.
|
|
3
|
+
import { Colorize } from '@rushstack/terminal';
|
|
4
|
+
const REQUIRED_FIELDS = [
|
|
5
|
+
// command is absent because it is not required
|
|
6
|
+
'id',
|
|
7
|
+
'task',
|
|
8
|
+
'package',
|
|
9
|
+
'dependencies',
|
|
10
|
+
'workingDirectory'
|
|
11
|
+
];
|
|
12
|
+
/*
|
|
13
|
+
* Get the operation id
|
|
14
|
+
*/
|
|
15
|
+
export function getOperationId(operation) {
|
|
16
|
+
const { associatedPhase: { name: task }, associatedProject: { packageName } } = operation;
|
|
17
|
+
return `${packageName}#${task}`;
|
|
18
|
+
}
|
|
19
|
+
export class GraphProcessor {
|
|
20
|
+
constructor(logger) {
|
|
21
|
+
this._logger = logger;
|
|
22
|
+
}
|
|
23
|
+
/*
|
|
24
|
+
* Convert the operationMap into an array of graph nodes with empty commands pruned
|
|
25
|
+
*/
|
|
26
|
+
processOperations(operations) {
|
|
27
|
+
const nodeMap = new Map();
|
|
28
|
+
for (const operation of operations) {
|
|
29
|
+
const entry = this._operationAsHashedEntry(operation);
|
|
30
|
+
nodeMap.set(entry.id, entry);
|
|
31
|
+
}
|
|
32
|
+
return this._pruneNoOps(nodeMap);
|
|
33
|
+
}
|
|
34
|
+
/*
|
|
35
|
+
* Validate that all dependencies exist
|
|
36
|
+
* Validate that all nodes have a non-empty command
|
|
37
|
+
* Print a message to the logger, and return true if the graph is valid
|
|
38
|
+
*/
|
|
39
|
+
validateGraph(entries) {
|
|
40
|
+
const entryIDs = new Set(entries.map((entry) => entry.id));
|
|
41
|
+
let isValid = true;
|
|
42
|
+
for (const entry of entries) {
|
|
43
|
+
for (const depId of entry.dependencies) {
|
|
44
|
+
if (!entryIDs.has(depId)) {
|
|
45
|
+
this._logger.emitError(new Error(`${entry.id} has a dependency on ${depId} which does not exist`));
|
|
46
|
+
isValid = false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (!entry.command) {
|
|
50
|
+
this._logger.emitError(new Error(`There is an empty command in ${entry.id}`));
|
|
51
|
+
isValid = false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (isValid) {
|
|
55
|
+
this._logger.terminal.writeLine(Colorize.green('All nodes have non-empty commands and dependencies which exist'));
|
|
56
|
+
}
|
|
57
|
+
const totalEdges = entries.reduce((acc, entry) => acc + entry.dependencies.length, 0);
|
|
58
|
+
this._logger.terminal.writeLine(`Graph has ${entries.length} nodes, ${totalEdges} edges`);
|
|
59
|
+
return isValid;
|
|
60
|
+
}
|
|
61
|
+
/*
|
|
62
|
+
* remove all entries with empty commands
|
|
63
|
+
* if an entry has a dependency with an empty command, it should inherit the dependencies of the empty command
|
|
64
|
+
*/
|
|
65
|
+
_pruneNoOps(inputNodeMap) {
|
|
66
|
+
// Cache for the non-empty upstream dependencies of each operation
|
|
67
|
+
const nonEmptyDependenciesByOperation = new Map();
|
|
68
|
+
function getNonEmptyDependencies(node) {
|
|
69
|
+
// If we've already computed this, return the cached result
|
|
70
|
+
let nonEmptyDependencies = nonEmptyDependenciesByOperation.get(node);
|
|
71
|
+
if (!nonEmptyDependencies) {
|
|
72
|
+
nonEmptyDependencies = new Set();
|
|
73
|
+
nonEmptyDependenciesByOperation.set(node, nonEmptyDependencies);
|
|
74
|
+
for (const dependencyID of node.dependencies) {
|
|
75
|
+
if (!inputNodeMap.get(dependencyID).command) {
|
|
76
|
+
// If the dependency is empty, recursively inherit its non-empty dependencies
|
|
77
|
+
for (const deepDependency of getNonEmptyDependencies(inputNodeMap.get(dependencyID))) {
|
|
78
|
+
nonEmptyDependencies.add(deepDependency);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
nonEmptyDependencies.add(dependencyID);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return nonEmptyDependencies;
|
|
87
|
+
}
|
|
88
|
+
const result = [];
|
|
89
|
+
for (const node of inputNodeMap.values()) {
|
|
90
|
+
const command = node.command;
|
|
91
|
+
if (command) {
|
|
92
|
+
const nonEmptyDependencySet = getNonEmptyDependencies(node);
|
|
93
|
+
result.push({
|
|
94
|
+
...node,
|
|
95
|
+
dependencies: Array.from(nonEmptyDependencySet),
|
|
96
|
+
command: command
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
/*
|
|
103
|
+
* Convert an operation into a graph node
|
|
104
|
+
*/
|
|
105
|
+
_operationAsHashedEntry(operation) {
|
|
106
|
+
const { runner, associatedPhase: { name: task }, associatedProject: {
|
|
107
|
+
// "package" is a reserved word
|
|
108
|
+
packageName, projectFolder: workingDirectory }, settings, dependencies: operationDependencies } = operation;
|
|
109
|
+
const dependencies = new Set();
|
|
110
|
+
for (const dependency of operationDependencies.values()) {
|
|
111
|
+
const id = getOperationId(dependency);
|
|
112
|
+
dependencies.add(id);
|
|
113
|
+
}
|
|
114
|
+
const node = {
|
|
115
|
+
id: getOperationId(operation),
|
|
116
|
+
task,
|
|
117
|
+
package: packageName,
|
|
118
|
+
dependencies,
|
|
119
|
+
workingDirectory,
|
|
120
|
+
command: runner === null || runner === void 0 ? void 0 : runner.commandToRun
|
|
121
|
+
};
|
|
122
|
+
if (settings === null || settings === void 0 ? void 0 : settings.disableBuildCacheForOperation) {
|
|
123
|
+
node.cacheable = false;
|
|
124
|
+
}
|
|
125
|
+
const missingFields = [];
|
|
126
|
+
for (const requiredField of REQUIRED_FIELDS) {
|
|
127
|
+
if (!node[requiredField]) {
|
|
128
|
+
missingFields.push(requiredField);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (missingFields.length > 0) {
|
|
132
|
+
this._logger.emitError(new Error(`Operation is missing required fields ${missingFields.join(', ')}: ${JSON.stringify(node)}`));
|
|
133
|
+
}
|
|
134
|
+
// the runner is a no-op if and only if the command is empty
|
|
135
|
+
if (!!(runner === null || runner === void 0 ? void 0 : runner.isNoOp) !== !node.command) {
|
|
136
|
+
this._logger.emitError(new Error(`${node.id}: Operation runner isNoOp does not match commandToRun existence`));
|
|
137
|
+
}
|
|
138
|
+
return node;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=GraphProcessor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GraphProcessor.js","sourceRoot":"","sources":["../src/GraphProcessor.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport type { Operation, ILogger } from '@rushstack/rush-sdk';\nimport type { ShellOperationRunner } from '@rushstack/rush-sdk/lib/logic/operations/ShellOperationRunner';\nimport { Colorize } from '@rushstack/terminal';\n\n/**\n * @example\n * ```\n * {\n * \"id\": \"@rushstack/node-core-library#_phase:build\",\n * \"task\": \"_phase:build\",\n * \"package\": \"@rushstack/node-core-library\",\n * \"dependencies\": [\n * \"@rushstack/eslint-patch#_phase:build\",\n * \"@rushstack/eslint-plugin#_phase:build\",\n * \"@rushstack/eslint-plugin-packlets#_phase:build\",\n * \"@rushstack/eslint-plugin-security#_phase:build\"\n * ],\n * \"workingDirectory\": \"/repo/libraries/node-core-library\",\n * \"command\": \"heft run --only build -- --clean --production --drop-graph ./src/examples/graph.json\"\n * }\n * ```\n *\n * See https://github.com/microsoft/BuildXL/blob/adf025c1b96b8106984928df3e9c30c8331bc8d6/Public/Src/Tools/JavaScript/Tool.RushGraphBuilder/src/RushBuildPluginGraph.ts\n *\n * @public\n */\nexport interface IGraphNode {\n /**\n * The unique id of the Pip\n *\n * @example\n * `@rushstack/node-core-library#_phase:build`\n */\n id: string;\n\n /**\n * The command to run during the Pip\n */\n command: string;\n\n /**\n * The working directory of the Pip\n */\n workingDirectory: string;\n\n /**\n * The project name associated with the Pip\n *\n * @example\n * `@rushstack/node-core-library`\n */\n package: string;\n\n /**\n * The task name of the Pip\n *\n * @example\n * `_phase:build`\n */\n task: string;\n\n /**\n * The IDs of the dependencies of the Pip. These are the {@link IGraphNode.id} properties of other Pips.\n */\n dependencies: string[];\n\n /**\n * If false, the Pip is uncacheable\n */\n cacheable?: false;\n}\n\ninterface IGraphNodeInternal extends Omit<IGraphNode, 'dependencies' | 'command'> {\n dependencies: ReadonlySet<string>;\n command: string | undefined;\n}\n\ntype NodeMap = ReadonlyMap<string, IGraphNodeInternal>;\n\nconst REQUIRED_FIELDS: Array<keyof IGraphNodeInternal> = [\n // command is absent because it is not required\n 'id',\n 'task',\n 'package',\n 'dependencies',\n 'workingDirectory'\n];\n\n/*\n * Get the operation id\n */\nexport function getOperationId(operation: Operation): string {\n const {\n associatedPhase: { name: task },\n associatedProject: { packageName }\n } = operation;\n return `${packageName}#${task}`;\n}\n\nexport class GraphProcessor {\n private readonly _logger: ILogger;\n\n public constructor(logger: ILogger) {\n this._logger = logger;\n }\n\n /*\n * Convert the operationMap into an array of graph nodes with empty commands pruned\n */\n public processOperations(operations: Set<Operation>): IGraphNode[] {\n const nodeMap: Map<string, IGraphNodeInternal> = new Map();\n for (const operation of operations) {\n const entry: IGraphNodeInternal = this._operationAsHashedEntry(operation);\n nodeMap.set(entry.id, entry);\n }\n\n return this._pruneNoOps(nodeMap);\n }\n\n /*\n * Validate that all dependencies exist\n * Validate that all nodes have a non-empty command\n * Print a message to the logger, and return true if the graph is valid\n */\n public validateGraph(entries: readonly Readonly<IGraphNode>[]): boolean {\n const entryIDs: Set<string> = new Set(entries.map((entry) => entry.id));\n let isValid: boolean = true;\n for (const entry of entries) {\n for (const depId of entry.dependencies) {\n if (!entryIDs.has(depId)) {\n this._logger.emitError(new Error(`${entry.id} has a dependency on ${depId} which does not exist`));\n isValid = false;\n }\n }\n\n if (!entry.command) {\n this._logger.emitError(new Error(`There is an empty command in ${entry.id}`));\n isValid = false;\n }\n }\n\n if (isValid) {\n this._logger.terminal.writeLine(\n Colorize.green('All nodes have non-empty commands and dependencies which exist')\n );\n }\n\n const totalEdges: number = entries.reduce((acc, entry) => acc + entry.dependencies.length, 0);\n this._logger.terminal.writeLine(`Graph has ${entries.length} nodes, ${totalEdges} edges`);\n return isValid;\n }\n\n /*\n * remove all entries with empty commands\n * if an entry has a dependency with an empty command, it should inherit the dependencies of the empty command\n */\n private _pruneNoOps(inputNodeMap: NodeMap): IGraphNode[] {\n // Cache for the non-empty upstream dependencies of each operation\n const nonEmptyDependenciesByOperation: Map<IGraphNodeInternal, Set<string>> = new Map();\n function getNonEmptyDependencies(node: IGraphNodeInternal): ReadonlySet<string> {\n // If we've already computed this, return the cached result\n let nonEmptyDependencies: Set<string> | undefined = nonEmptyDependenciesByOperation.get(node);\n if (!nonEmptyDependencies) {\n nonEmptyDependencies = new Set();\n nonEmptyDependenciesByOperation.set(node, nonEmptyDependencies);\n for (const dependencyID of node.dependencies) {\n if (!inputNodeMap.get(dependencyID)!.command) {\n // If the dependency is empty, recursively inherit its non-empty dependencies\n for (const deepDependency of getNonEmptyDependencies(inputNodeMap.get(dependencyID)!)) {\n nonEmptyDependencies.add(deepDependency);\n }\n } else {\n nonEmptyDependencies.add(dependencyID);\n }\n }\n }\n\n return nonEmptyDependencies;\n }\n\n const result: IGraphNode[] = [];\n for (const node of inputNodeMap.values()) {\n const command: string | undefined = node.command;\n if (command) {\n const nonEmptyDependencySet: ReadonlySet<string> = getNonEmptyDependencies(node);\n result.push({\n ...node,\n dependencies: Array.from(nonEmptyDependencySet),\n command: command\n });\n }\n }\n\n return result;\n }\n\n /*\n * Convert an operation into a graph node\n */\n private _operationAsHashedEntry(operation: Operation): IGraphNodeInternal {\n const {\n runner,\n associatedPhase: { name: task },\n associatedProject: {\n // \"package\" is a reserved word\n packageName,\n projectFolder: workingDirectory\n },\n settings,\n dependencies: operationDependencies\n } = operation;\n\n const dependencies: Set<string> = new Set();\n for (const dependency of operationDependencies.values()) {\n const id: string = getOperationId(dependency);\n dependencies.add(id);\n }\n\n const node: Partial<IGraphNodeInternal> = {\n id: getOperationId(operation),\n task,\n package: packageName,\n dependencies,\n workingDirectory,\n command: (runner as Partial<Pick<ShellOperationRunner, 'commandToRun'>>)?.commandToRun\n };\n\n if (settings?.disableBuildCacheForOperation) {\n node.cacheable = false;\n }\n\n const missingFields: (keyof IGraphNodeInternal)[] = [];\n for (const requiredField of REQUIRED_FIELDS) {\n if (!node[requiredField]) {\n missingFields.push(requiredField);\n }\n }\n\n if (missingFields.length > 0) {\n this._logger.emitError(\n new Error(`Operation is missing required fields ${missingFields.join(', ')}: ${JSON.stringify(node)}`)\n );\n }\n\n // the runner is a no-op if and only if the command is empty\n if (!!runner?.isNoOp !== !node.command) {\n this._logger.emitError(\n new Error(`${node.id}: Operation runner isNoOp does not match commandToRun existence`)\n );\n }\n\n return node as IGraphNodeInternal;\n }\n}\n"]}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
|
|
2
|
+
// See LICENSE in the project root for license information.
|
|
3
|
+
import { getOperationId } from './GraphProcessor';
|
|
4
|
+
const BANNED_KEYS = new Set([
|
|
5
|
+
'packageJsonEditor',
|
|
6
|
+
'_packageJson',
|
|
7
|
+
'_subspaces',
|
|
8
|
+
'subspace',
|
|
9
|
+
'rushConfiguration',
|
|
10
|
+
'_rushConfiguration',
|
|
11
|
+
'associatedParameters',
|
|
12
|
+
'_dependencyProjects'
|
|
13
|
+
]);
|
|
14
|
+
const ALLOWED_KEYS = new Set([
|
|
15
|
+
'associatedPhase',
|
|
16
|
+
'name',
|
|
17
|
+
'associatedProject',
|
|
18
|
+
'packageName',
|
|
19
|
+
'projectFolder',
|
|
20
|
+
'dependencies',
|
|
21
|
+
'runner',
|
|
22
|
+
'commandToRun',
|
|
23
|
+
'isNoOp'
|
|
24
|
+
]);
|
|
25
|
+
/*
|
|
26
|
+
* Filter Rush's build graph to remove repetitive and unimportant information. Useful if the schema ever changes, or needs to.
|
|
27
|
+
* Due to the fact that it includes all properties that are not banned, it is not guaranteed to be stable across versions.
|
|
28
|
+
* Should not be part of the critical path.
|
|
29
|
+
* @param obj - the object to filter
|
|
30
|
+
* @param depth - the maximum depth to recurse
|
|
31
|
+
* @param simplify - if true, will replace embedded operations with their operation id
|
|
32
|
+
*/
|
|
33
|
+
export function filterObjectForDebug(obj, depth = 10, simplify = false) {
|
|
34
|
+
const output = {};
|
|
35
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
36
|
+
if (BANNED_KEYS.has(key)) {
|
|
37
|
+
output[key] = `${key} truncated`;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
else if (typeof value === 'function') {
|
|
41
|
+
output[key] = `${key}()`;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
else if (value instanceof Set) {
|
|
45
|
+
output[key] = filterObjectForDebug(Array.from(value), Math.min(depth - 1, 5), true);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
else if (value instanceof Object) {
|
|
49
|
+
if (depth <= 0) {
|
|
50
|
+
output[key] = `${key} too deep`;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (simplify) {
|
|
54
|
+
const operationId = getOperationId(value);
|
|
55
|
+
if (operationId) {
|
|
56
|
+
output[key] = operationId;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
output[key] = filterObjectForDebug(value, depth - 1);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
output[key] = value;
|
|
64
|
+
}
|
|
65
|
+
return output;
|
|
66
|
+
}
|
|
67
|
+
export function filterObjectForTesting(obj, depth = 10, ignoreSets = false) {
|
|
68
|
+
const output = {};
|
|
69
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
70
|
+
if (!ALLOWED_KEYS.has(key) && !key.match(/^\d+$/)) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
else if (value instanceof Set) {
|
|
74
|
+
if (!ignoreSets) {
|
|
75
|
+
// Don't need sets inside sets
|
|
76
|
+
output[key] = Array.from(value).map((subValue) => filterObjectForTesting(subValue, Math.min(depth - 1, 5), true));
|
|
77
|
+
}
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
else if (value instanceof Object) {
|
|
81
|
+
if (depth <= 0) {
|
|
82
|
+
output[key] = `${key} too deep`;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
output[key] = filterObjectForTesting(value, depth - 1, ignoreSets);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
output[key] = value;
|
|
89
|
+
}
|
|
90
|
+
return output;
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=debugGraphFiltering.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"debugGraphFiltering.js","sourceRoot":"","sources":["../src/debugGraphFiltering.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport { getOperationId } from './GraphProcessor';\n\nconst BANNED_KEYS: ReadonlySet<string> = new Set([\n 'packageJsonEditor',\n '_packageJson',\n '_subspaces',\n 'subspace',\n 'rushConfiguration',\n '_rushConfiguration',\n 'associatedParameters',\n '_dependencyProjects'\n]);\n\nconst ALLOWED_KEYS: ReadonlySet<string> = new Set([\n 'associatedPhase',\n 'name',\n 'associatedProject',\n 'packageName',\n 'projectFolder',\n 'dependencies',\n 'runner',\n 'commandToRun',\n 'isNoOp'\n]);\n\n/*\n * Filter Rush's build graph to remove repetitive and unimportant information. Useful if the schema ever changes, or needs to.\n * Due to the fact that it includes all properties that are not banned, it is not guaranteed to be stable across versions.\n * Should not be part of the critical path.\n * @param obj - the object to filter\n * @param depth - the maximum depth to recurse\n * @param simplify - if true, will replace embedded operations with their operation id\n */\nexport function filterObjectForDebug(obj: object, depth: number = 10, simplify: boolean = false): object {\n const output: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n if (BANNED_KEYS.has(key)) {\n output[key] = `${key} truncated`;\n continue;\n } else if (typeof value === 'function') {\n output[key] = `${key}()`;\n continue;\n } else if (value instanceof Set) {\n output[key] = filterObjectForDebug(Array.from(value), Math.min(depth - 1, 5), true);\n continue;\n } else if (value instanceof Object) {\n if (depth <= 0) {\n output[key] = `${key} too deep`;\n continue;\n }\n\n if (simplify) {\n const operationId: string | undefined = getOperationId(value);\n if (operationId) {\n output[key] = operationId;\n continue;\n }\n }\n\n output[key] = filterObjectForDebug(value, depth - 1);\n continue;\n }\n\n output[key] = value;\n }\n\n return output;\n}\n\nexport function filterObjectForTesting(obj: object, depth: number = 10, ignoreSets: boolean = false): object {\n const output: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n if (!ALLOWED_KEYS.has(key) && !key.match(/^\\d+$/)) {\n continue;\n } else if (value instanceof Set) {\n if (!ignoreSets) {\n // Don't need sets inside sets\n output[key] = Array.from(value).map((subValue) =>\n filterObjectForTesting(subValue, Math.min(depth - 1, 5), true)\n );\n }\n\n continue;\n } else if (value instanceof Object) {\n if (depth <= 0) {\n output[key] = `${key} too deep`;\n continue;\n }\n\n output[key] = filterObjectForTesting(value, depth - 1, ignoreSets);\n continue;\n }\n\n output[key] = value;\n }\n\n return output;\n}\n"]}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
|
|
2
|
+
// See LICENSE in the project root for license information.
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { JsonFile } from '@rushstack/node-core-library';
|
|
5
|
+
import { GraphProcessor } from './GraphProcessor';
|
|
6
|
+
import { filterObjectForDebug, filterObjectForTesting } from './debugGraphFiltering';
|
|
7
|
+
const DEBUG_RUSH_BUILD_GRAPH_ENV_VAR_NAME = 'DEBUG_RUSH_BUILD_GRAPH';
|
|
8
|
+
export async function dropGraphAsync(options) {
|
|
9
|
+
const { operations, context, dropGraphPath, rushConfiguration: { commonTempFolder }, logger } = options;
|
|
10
|
+
if (process.env[DEBUG_RUSH_BUILD_GRAPH_ENV_VAR_NAME]) {
|
|
11
|
+
let filterFn;
|
|
12
|
+
const debugValue = process.env[DEBUG_RUSH_BUILD_GRAPH_ENV_VAR_NAME];
|
|
13
|
+
switch (process.env.DEBUG_RUSH_BUILD_GRAPH) {
|
|
14
|
+
case 'test': {
|
|
15
|
+
filterFn = filterObjectForTesting;
|
|
16
|
+
break;
|
|
17
|
+
}
|
|
18
|
+
case 'full': {
|
|
19
|
+
filterFn = filterObjectForDebug;
|
|
20
|
+
break;
|
|
21
|
+
}
|
|
22
|
+
default: {
|
|
23
|
+
throw new Error(`Invalid value for ${DEBUG_RUSH_BUILD_GRAPH_ENV_VAR_NAME}: ${debugValue}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (filterFn) {
|
|
27
|
+
const graphOut = [];
|
|
28
|
+
for (const operation of operations.keys()) {
|
|
29
|
+
graphOut.push(filterFn(operation));
|
|
30
|
+
}
|
|
31
|
+
const debugOutput = {
|
|
32
|
+
OperationMap: graphOut,
|
|
33
|
+
ICreateOperationsContext: filterFn(context)
|
|
34
|
+
};
|
|
35
|
+
const debugPathOut = `${path.dirname(dropGraphPath)}/debug-${path.basename(dropGraphPath)}`;
|
|
36
|
+
await JsonFile.saveAsync(debugOutput, debugPathOut, { ensureFolderExists: true });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const graphProcessor = new GraphProcessor(logger);
|
|
40
|
+
const nodes = graphProcessor.processOperations(operations);
|
|
41
|
+
const buildXLGraph = {
|
|
42
|
+
nodes,
|
|
43
|
+
repoSettings: {
|
|
44
|
+
commonTempFolder
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
await JsonFile.saveAsync(buildXLGraph, dropGraphPath, { ensureFolderExists: true });
|
|
48
|
+
return graphProcessor.validateGraph(buildXLGraph.nodes);
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=dropGraph.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dropGraph.js","sourceRoot":"","sources":["../src/dropGraph.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,2DAA2D;AAE3D,OAAO,IAAI,MAAM,WAAW,CAAC;AAG7B,OAAO,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AAGxD,OAAO,EAAmB,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAUrF,MAAM,mCAAmC,GAA6B,wBAAwB,CAAC;AAE/F,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,OAA0B;IAC7D,MAAM,EACJ,UAAU,EACV,OAAO,EACP,aAAa,EACb,iBAAiB,EAAE,EAAE,gBAAgB,EAAE,EACvC,MAAM,EACP,GAAG,OAAO,CAAC;IACZ,IAAI,OAAO,CAAC,GAAG,CAAC,mCAAmC,CAAC,EAAE,CAAC;QACrD,IAAI,QAA+D,CAAC;QACpE,MAAM,UAAU,GAAuB,OAAO,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC;QACxF,QAAQ,OAAO,CAAC,GAAG,CAAC,sBAAsB,EAAE,CAAC;YAC3C,KAAK,MAAM,CAAC,CAAC,CAAC;gBACZ,QAAQ,GAAG,sBAAsB,CAAC;gBAClC,MAAM;YACR,CAAC;YAED,KAAK,MAAM,CAAC,CAAC,CAAC;gBACZ,QAAQ,GAAG,oBAAoB,CAAC;gBAChC,MAAM;YACR,CAAC;YAED,OAAO,CAAC,CAAC,CAAC;gBACR,MAAM,IAAI,KAAK,CAAC,qBAAqB,mCAAmC,KAAK,UAAU,EAAE,CAAC,CAAC;YAC7F,CAAC;QACH,CAAC;QAED,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,QAAQ,GAAc,EAAE,CAAC;YAC/B,KAAK,MAAM,SAAS,IAAI,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC;gBAC1C,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC;YACrC,CAAC;YAED,MAAM,WAAW,GAAY;gBAC3B,YAAY,EAAE,QAAQ;gBACtB,wBAAwB,EAAE,QAAQ,CAAC,OAAO,CAAC;aAC5C,CAAC;YACF,MAAM,YAAY,GAAW,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,UAAU,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;YACpG,MAAM,QAAQ,CAAC,SAAS,CAAC,WAAW,EAAE,YAAY,EAAE,EAAE,kBAAkB,EAAE,IAAI,EAAE,CAAC,CAAC;QACpF,CAAC;IACH,CAAC;IAED,MAAM,cAAc,GAAmB,IAAI,cAAc,CAAC,MAAM,CAAC,CAAC;IAClE,MAAM,KAAK,GAAiB,cAAc,CAAC,iBAAiB,CAAC,UAAU,CAAC,CAAC;IACzE,MAAM,YAAY,GAAsB;QACtC,KAAK;QACL,YAAY,EAAE;YACZ,gBAAgB;SACjB;KACF,CAAC;IAEF,MAAM,QAAQ,CAAC,SAAS,CAAC,YAAY,EAAE,aAAa,EAAE,EAAE,kBAAkB,EAAE,IAAI,EAAE,CAAC,CAAC;IACpF,OAAO,cAAc,CAAC,aAAa,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;AAC1D,CAAC","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport path from 'node:path';\n\nimport type { ICreateOperationsContext, ILogger, Operation, RushConfiguration } from '@rushstack/rush-sdk';\nimport { JsonFile } from '@rushstack/node-core-library';\n\nimport type { IBuildXLRushGraph } from './DropBuildGraphPlugin';\nimport { type IGraphNode, GraphProcessor } from './GraphProcessor';\nimport { filterObjectForDebug, filterObjectForTesting } from './debugGraphFiltering';\n\nexport interface IDropGraphOptions {\n operations: Set<Operation>;\n context: ICreateOperationsContext;\n dropGraphPath: string;\n rushConfiguration: RushConfiguration;\n logger: ILogger;\n}\n\nconst DEBUG_RUSH_BUILD_GRAPH_ENV_VAR_NAME: 'DEBUG_RUSH_BUILD_GRAPH' = 'DEBUG_RUSH_BUILD_GRAPH';\n\nexport async function dropGraphAsync(options: IDropGraphOptions): Promise<boolean> {\n const {\n operations,\n context,\n dropGraphPath,\n rushConfiguration: { commonTempFolder },\n logger\n } = options;\n if (process.env[DEBUG_RUSH_BUILD_GRAPH_ENV_VAR_NAME]) {\n let filterFn: ((obj: object, depth?: number) => object) | undefined;\n const debugValue: string | undefined = process.env[DEBUG_RUSH_BUILD_GRAPH_ENV_VAR_NAME];\n switch (process.env.DEBUG_RUSH_BUILD_GRAPH) {\n case 'test': {\n filterFn = filterObjectForTesting;\n break;\n }\n\n case 'full': {\n filterFn = filterObjectForDebug;\n break;\n }\n\n default: {\n throw new Error(`Invalid value for ${DEBUG_RUSH_BUILD_GRAPH_ENV_VAR_NAME}: ${debugValue}`);\n }\n }\n\n if (filterFn) {\n const graphOut: unknown[] = [];\n for (const operation of operations.keys()) {\n graphOut.push(filterFn(operation));\n }\n\n const debugOutput: unknown = {\n OperationMap: graphOut,\n ICreateOperationsContext: filterFn(context)\n };\n const debugPathOut: string = `${path.dirname(dropGraphPath)}/debug-${path.basename(dropGraphPath)}`;\n await JsonFile.saveAsync(debugOutput, debugPathOut, { ensureFolderExists: true });\n }\n }\n\n const graphProcessor: GraphProcessor = new GraphProcessor(logger);\n const nodes: IGraphNode[] = graphProcessor.processOperations(operations);\n const buildXLGraph: IBuildXLRushGraph = {\n nodes,\n repoSettings: {\n commonTempFolder\n }\n };\n\n await JsonFile.saveAsync(buildXLGraph, dropGraphPath, { ensureFolderExists: true });\n return graphProcessor.validateGraph(buildXLGraph.nodes);\n}\n"]}
|
package/lib-esm/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
|
|
2
|
+
// See LICENSE in the project root for license information.
|
|
3
|
+
import { DropBuildGraphPlugin } from './DropBuildGraphPlugin';
|
|
4
|
+
export default DropBuildGraphPlugin;
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,2DAA2D;AAK3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,eAAe,oBAAoB,CAAC","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nexport type { IBuildXLRushGraph } from './DropBuildGraphPlugin';\nexport type { IGraphNode } from './GraphProcessor';\nexport type { IDropGraphPluginOptions } from './DropBuildGraphPlugin';\nimport { DropBuildGraphPlugin } from './DropBuildGraphPlugin';\nexport default DropBuildGraphPlugin;\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-04/schema#",
|
|
3
|
+
"title": "Configuration for the BuildXL graph drop plugin.",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"required": ["buildXLCommandNames"],
|
|
6
|
+
"properties": {
|
|
7
|
+
"buildXLCommandNames": {
|
|
8
|
+
"type": "array",
|
|
9
|
+
"description": "The names of the commands that will be used to run BuildXL",
|
|
10
|
+
"items": {
|
|
11
|
+
"type": "string"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rushstack/rush-buildxl-graph-plugin",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.169.0",
|
|
4
4
|
"description": "Rush plugin for generating a BuildXL graph.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -8,21 +8,45 @@
|
|
|
8
8
|
"directory": "rush-plugins/rush-buildxl-graph-plugin"
|
|
9
9
|
},
|
|
10
10
|
"homepage": "https://rushjs.io",
|
|
11
|
-
"main": "lib/index.js",
|
|
12
|
-
"
|
|
11
|
+
"main": "./lib-commonjs/index.js",
|
|
12
|
+
"module": "./lib-esm/index.js",
|
|
13
|
+
"types": "./lib-dts/index.d.ts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./lib-dts/index.d.ts",
|
|
17
|
+
"import": "./lib-esm/index.js",
|
|
18
|
+
"require": "./lib-commonjs/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./lib/*.schema.json": "./lib-commonjs/*.schema.json",
|
|
21
|
+
"./lib/*": {
|
|
22
|
+
"types": "./lib-dts/*.d.ts",
|
|
23
|
+
"import": "./lib-esm/*.js",
|
|
24
|
+
"require": "./lib-commonjs/*.js"
|
|
25
|
+
},
|
|
26
|
+
"./rush-plugin-manifest.json": "./rush-plugin-manifest.json",
|
|
27
|
+
"./package.json": "./package.json"
|
|
28
|
+
},
|
|
29
|
+
"typesVersions": {
|
|
30
|
+
"*": {
|
|
31
|
+
"lib/*": [
|
|
32
|
+
"lib-dts/*"
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
},
|
|
13
36
|
"license": "MIT",
|
|
14
37
|
"dependencies": {
|
|
15
|
-
"@rushstack/node-core-library": "5.
|
|
16
|
-
"@rushstack/
|
|
17
|
-
"@rushstack/
|
|
18
|
-
"@rushstack/
|
|
38
|
+
"@rushstack/node-core-library": "5.20.0",
|
|
39
|
+
"@rushstack/ts-command-line": "5.3.0",
|
|
40
|
+
"@rushstack/rush-sdk": "5.169.0",
|
|
41
|
+
"@rushstack/terminal": "0.22.0"
|
|
19
42
|
},
|
|
20
43
|
"devDependencies": {
|
|
21
44
|
"eslint": "~9.37.0",
|
|
22
|
-
"@microsoft/rush-lib": "5.
|
|
23
|
-
"@rushstack/heft": "1.
|
|
45
|
+
"@microsoft/rush-lib": "5.169.0",
|
|
46
|
+
"@rushstack/heft": "1.2.0",
|
|
24
47
|
"local-node-rig": "1.0.0"
|
|
25
48
|
},
|
|
49
|
+
"sideEffects": false,
|
|
26
50
|
"scripts": {
|
|
27
51
|
"build": "heft build --clean",
|
|
28
52
|
"start": "heft test --clean --watch",
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
{
|
|
5
5
|
"pluginName": "rush-buildxl-graph-plugin",
|
|
6
6
|
"description": "Plugin for providing access to Rush build graph.",
|
|
7
|
-
"entryPoint": "lib/index.js",
|
|
8
|
-
"optionsSchema": "lib/schemas/rush-buildxl-graph-plugin.schema.json"
|
|
7
|
+
"entryPoint": "./lib-commonjs/index.js",
|
|
8
|
+
"optionsSchema": "./lib-commonjs/schemas/rush-buildxl-graph-plugin.schema.json"
|
|
9
9
|
}
|
|
10
10
|
]
|
|
11
11
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|