hard-coded "src"
Opened this issue · 0 comments
github-actions commented
hard-coded "src"
import type { ExecutorContext, ProjectGraphProjectNode } from '@nrwl/devkit';
import { normalizePath, readJsonFile } from '@nrwl/devkit';
import {
copySync,
readdirSync,
readFileSync,
removeSync,
writeFileSync,
} from 'fs-extra';
import { join, relative } from 'path';
import type { NormalizedExecutorOptions } from '../executors/tsc/schema';
import { existsSync } from 'fs';
interface InlineProjectNode {
name: string;
root: string;
sourceRoot: string;
pathAlias: string;
buildOutputPath?: string;
}
export interface InlineProjectGraph {
nodes: Record<string, InlineProjectNode>;
externals: Record<string, InlineProjectNode>;
dependencies: Record<string, string[]>;
}
export function isInlineGraphEmpty(inlineGraph: InlineProjectGraph): boolean {
return Object.keys(inlineGraph.nodes).length === 0;
}
export function handleInliningBuild(
context: ExecutorContext,
options: NormalizedExecutorOptions,
tsConfigPath: string
): InlineProjectGraph {
const tsConfigJson = readJsonFile(tsConfigPath);
const pathAliases =
tsConfigJson['compilerOptions']['paths'] || readBasePathAliases(context);
const inlineGraph = createInlineGraph(context, options, pathAliases);
if (isInlineGraphEmpty(inlineGraph)) {
return inlineGraph;
}
buildInlineGraphExternals(context, inlineGraph, pathAliases);
return inlineGraph;
}
export function postProcessInlinedDependencies(
outputPath: string,
parentOutputPath: string,
inlineGraph: InlineProjectGraph
) {
if (isInlineGraphEmpty(inlineGraph)) {
return;
}
const parentDistPath = join(outputPath, parentOutputPath);
// move parentOutput
movePackage(parentDistPath, outputPath);
const inlinedDepsDestOutputRecord: Record<string, string> = {};
// move inlined outputs
for (const inlineDependenciesNames of Object.values(
inlineGraph.dependencies
)) {
for (const inlineDependenciesName of inlineDependenciesNames) {
const inlineDependency = inlineGraph.nodes[inlineDependenciesName];
const depOutputPath =
inlineDependency.buildOutputPath ||
join(outputPath, inlineDependency.root);
const destDepOutputPath = join(outputPath, inlineDependency.name);
const isBuildable = !!inlineDependency.buildOutputPath;
if (isBuildable) {
copySync(depOutputPath, destDepOutputPath, { overwrite: true });
} else {
movePackage(depOutputPath, destDepOutputPath);
}
// TODO: hard-coded "src"
inlinedDepsDestOutputRecord[inlineDependency.pathAlias] =
destDepOutputPath + '/src';
}
}
updateImports(outputPath, inlinedDepsDestOutputRecord);
}
function readBasePathAliases(context: ExecutorContext) {
return readJsonFile(getRootTsConfigPath(context))?.['compilerOptions'][
'paths'
];
}
export function getRootTsConfigPath(context: ExecutorContext): string | null {
for (const tsConfigName of ['tsconfig.base.json', 'tsconfig.json']) {
const tsConfigPath = join(context.root, tsConfigName);
if (existsSync(tsConfigPath)) {
return tsConfigPath;
}
}
throw new Error(
'Could not find a root tsconfig.json or tsconfig.base.json file.'
);
}
function emptyInlineGraph(): InlineProjectGraph {
return { nodes: {}, externals: {}, dependencies: {} };
}
function projectNodeToInlineProjectNode(
projectNode: ProjectGraphProjectNode,
pathAlias = '',
buildOutputPath = ''
): InlineProjectNode {
return {
name: projectNode.name,
root: projectNode.data.root,
sourceRoot: projectNode.data.sourceRoot,
pathAlias,
buildOutputPath,
};
}
function createInlineGraph(
context: ExecutorContext,
options: NormalizedExecutorOptions,
pathAliases: Record<string, string[]>,
projectName: string = context.projectName,
inlineGraph: InlineProjectGraph = emptyInlineGraph()
) {
if (options.external == null && options.internal == null) return inlineGraph;
const projectDependencies =
context.projectGraph.dependencies[projectName] || [];
if (projectDependencies.length === 0) return inlineGraph;
if (!inlineGraph.nodes[projectName]) {
inlineGraph.nodes[projectName] = projectNodeToInlineProjectNode(
context.projectGraph.nodes[projectName]
);
}
const implicitDependencies =
context.projectGraph.nodes[projectName].data.implicitDependencies || [];
for (const projectDependency of projectDependencies) {
// skip npm packages
if (projectDependency.target.startsWith('npm')) {
continue;
}
// skip implicitDependencies
if (implicitDependencies.includes(projectDependency.target)) {
continue;
}
const pathAlias = getPathAliasForPackage(
context.projectGraph.nodes[projectDependency.target],
pathAliases
);
const buildOutputPath = getBuildOutputPath(
projectDependency.target,
context,
options
);
const shouldInline =
/**
* if some buildable libraries are marked as internal,
*/
options.internal?.includes(projectDependency.target) ||
/**
* if all buildable libraries are marked as external,
* then push the project dependency that doesn't have a build target
*/
(options.external === 'all' && !buildOutputPath) ||
/**
* if all buildable libraries are marked as internal,
* then push every project dependency to be inlined
*/
options.external === 'none' ||
/**
* if some buildable libraries are marked as external,
* then push the project dependency that IS NOT marked as external OR doesn't have a build target
*/
(Array.isArray(options.external) &&
options.external.length > 0 &&
!options.external.includes(projectDependency.target)) ||
!buildOutputPath;
console.log(shouldInline, projectDependency.target);
if (shouldInline) {
inlineGraph.dependencies[projectName] ??= [];
inlineGraph.dependencies[projectName].push(projectDependency.target);
}
inlineGraph.nodes[projectDependency.target] =
projectNodeToInlineProjectNode(
context.projectGraph.nodes[projectDependency.target],
pathAlias,
buildOutputPath
);
if (
context.projectGraph.dependencies[projectDependency.target].length > 0
) {
inlineGraph = createInlineGraph(
context,
options,
pathAliases,
projectDependency.target,
inlineGraph
);
}
}
return inlineGraph;
}
function buildInlineGraphExternals(
context: ExecutorContext,
inlineProjectGraph: InlineProjectGraph,
pathAliases: Record<string, string[]>
) {
const allNodes = { ...context.projectGraph.nodes };
for (const [parent, dependencies] of Object.entries(
inlineProjectGraph.dependencies
)) {
if (allNodes[parent]) {
delete allNodes[parent];
}
for (const dependencyName of dependencies) {
const dependencyNode = inlineProjectGraph.nodes[dependencyName];
// buildable is still external even if it is a dependency
if (dependencyNode.buildOutputPath) {
continue;
}
if (allNodes[dependencyName]) {
delete allNodes[dependencyName];
}
}
}
for (const [projectName, projectNode] of Object.entries(allNodes)) {
if (!inlineProjectGraph.externals[projectName]) {
inlineProjectGraph.externals[projectName] =
projectNodeToInlineProjectNode(
projectNode,
getPathAliasForPackage(projectNode, pathAliases)
);
}
}
}
function movePackage(from: string, to: string) {
if (from === to) return;
copySync(from, to, { overwrite: true });
removeSync(from);
}
function updateImports(
destOutputPath: string,
inlinedDepsDestOutputRecord: Record<string, string>
) {
const importRegex = new RegExp(
Object.keys(inlinedDepsDestOutputRecord)
.map((pathAlias) => `["'](${pathAlias})["']`)
.join('|'),
'g'
);
recursiveUpdateImport(
destOutputPath,
importRegex,
inlinedDepsDestOutputRecord
);
}
function recursiveUpdateImport(
dirPath: string,
importRegex: RegExp,
inlinedDepsDestOutputRecord: Record<string, string>,
rootParentDir?: string
) {
const files = readdirSync(dirPath, { withFileTypes: true });
for (const file of files) {
// only check .js and .d.ts files
if (
file.isFile() &&
(file.name.endsWith('.js') || file.name.endsWith('.d.ts'))
) {
const filePath = join(dirPath, file.name);
const fileContent = readFileSync(filePath, 'utf-8');
const updatedContent = fileContent.replace(importRegex, (matched) => {
const result = matched.replace(/['"]/g, '');
// If a match is the same as the rootParentDir, we're checking its own files so we return the matched as in no changes.
if (result === rootParentDir) return matched;
const importPath = `"${relative(
dirPath,
inlinedDepsDestOutputRecord[result]
)}"`;
return normalizePath(importPath);
});
writeFileSync(filePath, updatedContent);
} else if (file.isDirectory()) {
recursiveUpdateImport(
join(dirPath, file.name),
importRegex,
inlinedDepsDestOutputRecord,
rootParentDir || file.name
);
}
}
}
function getPathAliasForPackage(
packageNode: ProjectGraphProjectNode,
pathAliases: Record<string, string[]>
): string {
if (!packageNode) return '';
for (const [alias, paths] of Object.entries(pathAliases)) {
if (paths.some((path) => path.includes(packageNode.data.root))) {
return alias;
}
}
return '';
}
function getBuildOutputPath(
projectName: string,
context: ExecutorContext,
options: NormalizedExecutorOptions
): string {
const projectTargets = context.projectGraph.nodes[projectName]?.data?.targets;
if (!projectTargets) return '';
const buildTarget = options.externalBuildTargets.find(
(buildTarget) => projectTargets[buildTarget]
);
return buildTarget ? projectTargets[buildTarget].options['outputPath'] : '';
}
d1358f63ec92179924a0595d64a6c9a0a8228723