|  | /** | 
|  | * @license | 
|  | * Copyright (C) 2020 The Android Open Source Project | 
|  | * | 
|  | * Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | * you may not use this file except in compliance with the License. | 
|  | * You may obtain a copy of the License at | 
|  | * | 
|  | * http://www.apache.org/licenses/LICENSE-2.0 | 
|  | * | 
|  | * Unless required by applicable law or agreed to in writing, software | 
|  | * distributed under the License is distributed on an "AS IS" BASIS, | 
|  | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | * See the License for the specific language governing permissions and | 
|  | * limitations under the License. | 
|  | */ | 
|  |  | 
|  | import { PackageName, PackageVersion, DirPath, FilePath } from "./base-types"; | 
|  | import {fail} from "./utils"; | 
|  | import * as path from "path"; | 
|  | import * as fs from "fs"; | 
|  |  | 
|  | /** | 
|  | * Describe one installed package from node_modules | 
|  | */ | 
|  | export interface InstalledPackage { | 
|  | /** Package name (it is calculated based on path to module) */ | 
|  | name: PackageName; | 
|  | /** Package version from package.json */ | 
|  | version: PackageVersion; | 
|  | /** | 
|  | * Path to the top-level package directory, where package.json is placed | 
|  | * This path is relative to process working directory (because bazel pass all paths relative to it) | 
|  | */ | 
|  | rootPath: DirPath; | 
|  | /** All files in package. Path is relative to rootPath */ | 
|  | files: FilePath[]; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Calculates all installed packages from a list of files | 
|  | * It is expected, that the addPackageJson method is called first for | 
|  | * all package.json files first and then the addFile method is called for all files (including package.json) | 
|  | */ | 
|  | export class InsalledPackagesBuilder { | 
|  | private readonly rootPathToPackageMap: Map<DirPath, InstalledPackage> = new Map(); | 
|  |  | 
|  | public constructor(private readonly nonPackages: Set<string>) { | 
|  | } | 
|  |  | 
|  | public addPackageJson(packageJsonPath: string) { | 
|  | const pack = this.createInstalledPackage(packageJsonPath); | 
|  | if (!pack) return; | 
|  | this.rootPathToPackageMap.set(pack.rootPath, pack) | 
|  | } | 
|  | public addFile(file: string) { | 
|  | const pack = this.findPackageForFile(file)!; | 
|  | pack.files.push(path.relative(pack.rootPath, file)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Create new InstalledPackage. | 
|  | *  The name of a package is a relative path to the closest node_modules parent. | 
|  | * For example for the packageJsonFile='/a/node_modules/b/node_modules/d/e/package.json' | 
|  | * the package name is 'd/e' | 
|  | */ | 
|  | private createInstalledPackage(packageJsonFile: string): InstalledPackage | undefined { | 
|  | const nameParts: Array<string> = []; | 
|  | const rootPath = path.dirname(packageJsonFile); | 
|  | let currentDir = rootPath; | 
|  | while(currentDir != "") { | 
|  | const partName = path.basename(currentDir); | 
|  | if(partName === "node_modules") { | 
|  | const packageName = nameParts.reverse().join("/"); | 
|  | const version = JSON.parse(fs.readFileSync(packageJsonFile, {encoding: 'utf-8'}))["version"]; | 
|  | if(!version) { | 
|  | if (this.nonPackages.has(packageName)) { | 
|  | return undefined; | 
|  | } | 
|  | fail(`Can't get version for ${packageJsonFile}`) | 
|  | } | 
|  | return { | 
|  | name: packageName, | 
|  | rootPath: rootPath, | 
|  | version: version, | 
|  | files: [] | 
|  | }; | 
|  | } | 
|  | nameParts.push(partName); | 
|  | currentDir = path.dirname(currentDir); | 
|  | } | 
|  | fail(`Can't create package info for '${packageJsonFile}'`) | 
|  | } | 
|  |  | 
|  | private findPackageForFile(filePath: FilePath): InstalledPackage { | 
|  | let currentDir = path.dirname(filePath); | 
|  | while(currentDir != "") { | 
|  | if(this.rootPathToPackageMap.has(currentDir)) { | 
|  | return this.rootPathToPackageMap.get(currentDir)!; | 
|  | } | 
|  | currentDir = path.dirname(currentDir); | 
|  | } | 
|  | fail(`Can't find package for '${filePath}'`); | 
|  | } | 
|  |  | 
|  | public build(): InstalledPackage[] { | 
|  | return [...this.rootPathToPackageMap.values()]; | 
|  | } | 
|  | } |