import { LibraryConfiguration } from '@allianz/taly-core/schemas';
import {
joinPathFragments,
logger,
readProjectConfiguration,
Tree,
updateProjectConfiguration
} from '@nx/devkit';
interface AssetPattern {
glob: string;
input: string;
output: string;
}
interface AssetPatternWithRoot extends AssetPattern {
libraryRoot: string;
}
export function addLibAssetsToBuildTarget(
tree: Tree,
projectName: string,
libraries: LibraryConfiguration[]
) {
const project = readProjectConfiguration(tree, projectName);
const assetConfigsFromLibs = createAssetConfigsFromLibs(tree, libraries);
if (assetConfigsFromLibs?.length === 0) {
return;
}
const buildTarget = project.targets?.['build-generated-app'];
if (!buildTarget) {
logger.warn(
`⚠️ No "build-generated-app" target in project "${projectName}". Cannot add library assets to the "build-generated-app" target. Please consider adding a "build-generated-app" target.`
);
return;
}
const existingAssets: (string | AssetPattern)[] = buildTarget.options?.['assets'] ?? [];
const { assetsToAdd, assetsToUpdate } = getMismatchedAssets(existingAssets, assetConfigsFromLibs);
if (assetsToAdd.length === 0 && assetsToUpdate.length === 0) {
return;
}
let updatedAssets = structuredClone(existingAssets);
// remove all the assets from target config that are going to be updated with new values
if (assetsToUpdate.length > 0) {
updatedAssets = updatedAssets.map((existingAsset) => {
if (typeof existingAsset === 'string') return existingAsset;
// we only want to keep the assets that don't need to be updated
const assetToUpdateWithRoot = assetsToUpdate.find(({ libraryRoot }) =>
existingAsset.input.includes(libraryRoot)
);
if (!assetToUpdateWithRoot) return existingAsset;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { libraryRoot, ...assetToUpdate } = assetToUpdateWithRoot;
return assetToUpdate;
});
}
// add new assets while removing the `libraryRoot` property
updatedAssets.push(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
...assetsToAdd.map(({ libraryRoot, ...rest }) => rest)
);
buildTarget.options ??= {};
buildTarget.options['assets'] = updatedAssets;
updateProjectConfiguration(tree, projectName, project);
logger.info(
"This updated your project's build target configuration with some missing/outdated library assets. Please make sure to commit these changes."
);
}
function getMismatchedAssets(
existingAssets: (string | AssetPattern)[],
assetConfigsFromLibs: AssetPatternWithRoot[]
) {
const existingAssetsObjects: AssetPattern[] = existingAssets.filter(
(asset): asset is AssetPattern => typeof asset !== 'string'
);
const assetsToAdd = assetConfigsFromLibs.filter(
(asset) => !existingAssetsObjects.some(({ input }) => input.includes(asset.libraryRoot))
);
const assetsToUpdate = assetConfigsFromLibs.filter((createdAsset) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { libraryRoot, ...createdAssetConfigWithoutRoot } = createdAsset;
return existingAssetsObjects.some(
(existingAsset) =>
existingAsset.input.includes(createdAsset.libraryRoot) &&
JSON.stringify(createdAssetConfigWithoutRoot) !== JSON.stringify(existingAsset)
);
});
return {
assetsToAdd,
assetsToUpdate
};
}
function createAssetConfigsFromLibs(
tree: Tree,
libraries: LibraryConfiguration[]
): AssetPatternWithRoot[] {
const result = libraries.reduce((acc: AssetPatternWithRoot[], lib: LibraryConfiguration) => {
const libraryRoot = getLibraryRoot(tree, lib.package);
const libraryJsonPath = joinPathFragments(libraryRoot, 'library.json');
if (!tree.exists(libraryJsonPath)) {
return acc;
}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const libraryJsonContent = tree.read(libraryJsonPath, 'utf-8')!;
const libraryJson = JSON.parse(libraryJsonContent);
if (!libraryJson.assets) {
return acc;
}
acc.push({
glob: '**/*',
input: `${libraryRoot}/${libraryJson.assets}`,
output: `/assets/`,
libraryRoot: libraryRoot
});
return acc;
}, []);
return result;
}
function getLibraryRoot(tree: Tree, packageName: string) {
try {
const internalLibraryRoot = readProjectConfiguration(tree, packageName).root;
return internalLibraryRoot;
} catch {
const externalLibraryRoot = joinPathFragments('node_modules', packageName);
return externalLibraryRoot;
}
}