/** * @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 * as fs from "fs"; import * as path from "path"; import {FileUtils} from "../utils/file-utils"; import { Redirect, isRedirectToNodeModule, isRedirectToDir, RedirectToNodeModule, PathRedirect } from "./redirects"; export class HtmlFileUtils { public static getPathRelativeToRoot(parentHtml: string, fileHref: string): string { if (fileHref.startsWith('/')) { return fileHref.substring(1); } return path.join(path.dirname(parentHtml), fileHref); } public static getImportPathRelativeToParent(rootDir: string, parentFile: string, importPath: string) { if (importPath.startsWith('/')) { importPath = importPath.substr(1); } const parentDir = path.dirname( path.resolve(path.join(rootDir, parentFile))); const fullImportPath = path.resolve(path.join(rootDir, importPath)); const relativePath = path.relative(parentDir, fullImportPath); return relativePath.startsWith('../') ? relativePath : "./" + relativePath; } } interface RedirectForFile { to: PathRedirect; pathToFile: string; } interface ResolvedPath { target: string; insideNodeModules: boolean; } /** RedirectsResolver based on the list of redirects, calculates * new import path */ export class RedirectsResolver { public constructor(private readonly redirects: Redirect[]) { } /** resolve returns new path instead of pathRelativeToRoot; */ public resolve(pathRelativeToRoot: string, resolveNodeModules: boolean): ResolvedPath { const redirect = this.findRedirect(pathRelativeToRoot); if (!redirect) { return {target: pathRelativeToRoot, insideNodeModules: false}; } if (isRedirectToNodeModule(redirect.to)) { return { target: resolveNodeModules ? RedirectsResolver.resolveNodeModuleFile(redirect.to, redirect.pathToFile) : pathRelativeToRoot, insideNodeModules: resolveNodeModules }; } if (isRedirectToDir(redirect.to)) { let newDir = redirect.to.dir; if (!newDir.endsWith('/')) { newDir = newDir + '/'; } return {target: `${newDir}${redirect.pathToFile}`, insideNodeModules: false} } throw new Error(`Invalid redirect for path: ${pathRelativeToRoot}`); } private static resolveNodeModuleFile(npmRedirect: RedirectToNodeModule, pathToFile: string): string { if(npmRedirect.files && npmRedirect.files[pathToFile]) { pathToFile = npmRedirect.files[pathToFile]; } return `${npmRedirect.npm_module}/${pathToFile}`; } private findRedirect(relativePathToRoot: string): RedirectForFile | undefined { if(!relativePathToRoot.startsWith('/')) { relativePathToRoot = '/' + relativePathToRoot; } for(const redirect of this.redirects) { const normalizedFrom = redirect.from + (redirect.from.endsWith('/') ? '' : '/'); if(relativePathToRoot.startsWith(normalizedFrom)) { return { to: redirect.to, pathToFile: relativePathToRoot.substring(normalizedFrom.length) }; } } return undefined; } }