| /** | |
| * @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 addPackageJson(packageJsonPath: string) { | |
| const pack = this.createInstalledPackage(packageJsonPath); | |
| 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 { | |
| const nameParts: Array<string> = []; | |
| const rootPath = path.dirname(packageJsonFile); | |
| let currentDir = rootPath; | |
| while(currentDir != "") { | |
| const partName = path.basename(currentDir); | |
| if(partName === "node_modules") { | |
| const version = JSON.parse(fs.readFileSync(packageJsonFile, {encoding: 'utf-8'}))["version"]; | |
| if(!version) { | |
| fail(`Can't get version for ${packageJsonFile}`) | |
| } | |
| return { | |
| name: nameParts.reverse().join("/"), | |
| 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()]; | |
| } | |
| } |