ipsos-components
Version:
Material Design components for Angular
131 lines (111 loc) • 4.81 kB
text/typescript
import {join} from 'path';
import {readdirSync, lstatSync, existsSync} from 'fs';
import {spawnSync} from 'child_process';
import {BuildPackage} from './build-package';
import {platform} from 'os';
/**
* Gets secondary entry-points for a given package in the order they should be built.
*
* This currently assumes that every directory under a package should be an entry-point except for
* specifically black-listed directories.
*
* @param pkg The package for which to get entry points, e.g., 'cdk'.
* @returns An array of secondary entry-points names, e.g., ['a11y', 'bidi', ...]
*/
export function getSecondaryEntryPointsForPackage(pkg: BuildPackage) {
const packageName = pkg.name;
const packageDir = pkg.sourceDir;
console.log(pkg.sourceDir);
// Get the list of all entry-points as the list of directories in the package that have a
// tsconfig-build.json
const entryPoints = getSubdirectoryNames(packageDir)
.filter(d => existsSync(join(packageDir, d, 'tsconfig-build.json')));
// Create nodes that comprise the build graph.
const buildNodes: BuildNode[] = entryPoints.map(p => ({name: p, deps: [], depth: 0}));
// Create a lookup for name -> build graph node.
const nodeLookup = buildNodes.reduce((lookup, node) => {
return lookup.set(node.name, node);
}, new Map<string, BuildNode>());
// Regex used to extract entry-point name from an import statement referencing that entry-point.
// E.g., extract "portal" from "from '@angular/cdk/portal';".
const importRegex = new RegExp(`${packageName}/(.+)';`);
// Update the deps for each node to point to the appropriate BuildNodes.
buildNodes.forEach(node => {
const importStatementFindCommand = buildPackageImportStatementFindCommand(
join(packageDir, node.name), packageName);
// Look for any imports that reference this same umbrella package and get the corresponding
// BuildNode for each by looking at the import statements with grep.
node.deps = spawnSync(importStatementFindCommand.binary, importStatementFindCommand.args)
.stdout
.toString()
.split('\n')
.filter(n => {console.log("buildNodes:"+n);return n})
.map(importStatement => importStatement.match(importRegex)![1])
.filter(n => nodeLookup.has(n) && n !== node.name)
.map(depName => nodeLookup.get(depName)!) || [];
});
// Concatenate the build order for each node into one global build order.
// Duplicates are automatically omitted by getBuildOrder.
const buildOrder = buildNodes.reduce((order: BuildNode[], node) => {
return [...order, ...getBuildOrder(node)];
}, []);
return partitionNodesByDepth(buildOrder).map(level => level.map(node => node.name));
}
/** Gets the build order for node with DFS. As a side-effect, sets the depth on visited nodes. */
function getBuildOrder(node: BuildNode): BuildNode[] {
if (node.visited) {
return [];
}
let buildOrder: BuildNode[] = [];
for (const dep of node.deps) {
buildOrder = [...buildOrder, ...getBuildOrder(dep)];
node.depth = node.deps.reduce((maxDepth, d) => Math.max(d.depth + 1, maxDepth), -1);
}
node.visited = true;
return [...buildOrder, node];
}
/** Gets the names of all subdirectories for a given path. */
export function getSubdirectoryNames(dir: string): string[] {
return readdirSync(dir).filter(f => lstatSync(join(dir, f)).isDirectory());
}
/** A node in the build graph of a package's entry-points. */
interface BuildNode {
name: string;
deps: BuildNode[];
visited?: boolean;
depth: number;
}
/**
* Partitions nodes into groups by depth. For example,
* [{name: a11y, depth: 1}, {name: bidi, depth: 0}, {name: platform, depth: 0}]
* =>
* [ [{name: bidi, depth: 0}, {name: platform, depth: 0}], [{name: a11y, depth: 1}] ]
*/
function partitionNodesByDepth(nodes: BuildNode[]): BuildNode[][] {
const result: BuildNode[][] = [[]];
let lastDepth = 0;
nodes.sort((a, b) => a.depth - b.depth).forEach(node => {
if (node.depth === lastDepth) {
result[result.length - 1].push(node);
} else {
result.push([node]);
lastDepth = node.depth;
}
});
console.log(result);
return result;
}
/** Builds the command that will be executed to find all import statements for a package. */
function buildPackageImportStatementFindCommand(searchDirectory: string, packageName: string) {
if (platform() === 'win32') {
return {
binary: 'findstr',
args: ['/r', `from.'@angular/${packageName}/.*'`, `${searchDirectory}\\*.ts`]
};
} else {
return {
binary: 'grep',
args: ['-Eroh', '--include', '*.ts', `from '@angular/${packageName}/.+';`, searchDirectory]
};
}
}