| /** |
| * @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()]; |
| } |
| } |