|  | 
|  | 1 | +/** | 
|  | 2 | + * @license | 
|  | 3 | + * Copyright Google LLC All Rights Reserved. | 
|  | 4 | + * | 
|  | 5 | + * Use of this source code is governed by an MIT-style license that can be | 
|  | 6 | + * found in the LICENSE file at https://angular.dev/license | 
|  | 7 | + */ | 
|  | 8 | + | 
|  | 9 | +/** | 
|  | 10 | + * Forked from https://github.com/aspect-build/rules_esbuild/blob/e4e49d3354cbf7087c47ac9c5f2e6fe7f5e398d3/esbuild/private/plugins/bazel-sandbox.js | 
|  | 11 | + */ | 
|  | 12 | + | 
|  | 13 | +import { join } from 'node:path'; | 
|  | 14 | +import type { Plugin, PluginBuild, ResolveOptions, ResolveResult } from 'esbuild'; | 
|  | 15 | + | 
|  | 16 | +// Under Bazel, esbuild will follow symlinks out of the sandbox when the sandbox is enabled. See https://github.com/aspect-build/rules_esbuild/issues/58. | 
|  | 17 | +// This plugin using a separate resolver to detect if the the resolution has left the execroot (which is the root of the sandbox | 
|  | 18 | +// when sandboxing is enabled) and patches the resolution back into the sandbox. | 
|  | 19 | +export function createBazelSandboxPlugin(): Plugin { | 
|  | 20 | +  return { | 
|  | 21 | +    name: 'bazel-sandbox', | 
|  | 22 | +    setup(build) { | 
|  | 23 | +      build.onResolve({ filter: /./ }, async ({ path: importPath, ...otherOptions }) => { | 
|  | 24 | +        // NB: these lines are to prevent infinite recursion when we call `build.resolve`. | 
|  | 25 | +        if (otherOptions.pluginData) { | 
|  | 26 | +          if (otherOptions.pluginData.executedSandboxPlugin) { | 
|  | 27 | +            return; | 
|  | 28 | +          } | 
|  | 29 | +        } else { | 
|  | 30 | +          otherOptions.pluginData = {}; | 
|  | 31 | +        } | 
|  | 32 | +        otherOptions.pluginData.executedSandboxPlugin = true; | 
|  | 33 | + | 
|  | 34 | +        const bindir = process.env.BAZEL_BINDIR ?? ''; | 
|  | 35 | +        const execroot = process.env.JS_BINARY__EXECROOT ?? ''; | 
|  | 36 | + | 
|  | 37 | +        console.log(`DEBUG: [bazel-sandbox] detected bindir ${bindir}`); | 
|  | 38 | +        console.log(`DEBUG: [bazel-sandbox] detected execroot ${execroot}`); | 
|  | 39 | + | 
|  | 40 | +        return await resolveInExecroot({ build, bindir, execroot, importPath, otherOptions }); | 
|  | 41 | +      }); | 
|  | 42 | +    }, | 
|  | 43 | +  }; | 
|  | 44 | +} | 
|  | 45 | + | 
|  | 46 | +interface ResolveInExecrootOptions { | 
|  | 47 | +  build: PluginBuild; | 
|  | 48 | +  bindir: string; | 
|  | 49 | +  execroot: string; | 
|  | 50 | +  importPath: string; | 
|  | 51 | +  otherOptions: ResolveOptions; | 
|  | 52 | +} | 
|  | 53 | + | 
|  | 54 | +async function resolveInExecroot({ | 
|  | 55 | +  build, | 
|  | 56 | +  bindir, | 
|  | 57 | +  execroot, | 
|  | 58 | +  importPath, | 
|  | 59 | +  otherOptions, | 
|  | 60 | +}: ResolveInExecrootOptions): Promise<ResolveResult> { | 
|  | 61 | +  const result = await build.resolve(importPath, otherOptions); | 
|  | 62 | + | 
|  | 63 | +  if (result.errors && result.errors.length) { | 
|  | 64 | +    // There was an error resolving, just return the error as-is. | 
|  | 65 | +    return result; | 
|  | 66 | +  } | 
|  | 67 | + | 
|  | 68 | +  if ( | 
|  | 69 | +    !result.path.startsWith('.') && | 
|  | 70 | +    !result.path.startsWith('/') && | 
|  | 71 | +    !result.path.startsWith('\\') | 
|  | 72 | +  ) { | 
|  | 73 | +    // Not a relative or absolute path. Likely a module resolution that is marked "external" | 
|  | 74 | +    return result; | 
|  | 75 | +  } | 
|  | 76 | + | 
|  | 77 | +  // If esbuild attempts to leave the execroot, map the path back into the execroot. | 
|  | 78 | +  if (!result.path.startsWith(execroot)) { | 
|  | 79 | +    // If it tried to leave bazel-bin, error out completely. | 
|  | 80 | +    if (!result.path.includes(bindir)) { | 
|  | 81 | +      throw new Error( | 
|  | 82 | +        `Error: esbuild resolved a path outside of BAZEL_BINDIR (${bindir}): ${result.path}`, | 
|  | 83 | +      ); | 
|  | 84 | +    } | 
|  | 85 | +    // Otherwise remap the bindir-relative path | 
|  | 86 | +    const correctedPath = join(execroot, result.path.substring(result.path.indexOf(bindir))); | 
|  | 87 | +    if (!!process.env.JS_BINARY__LOG_DEBUG) { | 
|  | 88 | +      console.error( | 
|  | 89 | +        `DEBUG: [bazel-sandbox] correcting esbuild resolution ${result.path} that left the sandbox to ${correctedPath}.`, | 
|  | 90 | +      ); | 
|  | 91 | +    } | 
|  | 92 | +    result.path = correctedPath; | 
|  | 93 | +  } | 
|  | 94 | + | 
|  | 95 | +  console.log(`DEBUG: [bazel-sandbox] import: ${importPath}`); | 
|  | 96 | +  console.log(`DEBUG: [bazel-sandbox] result: ${result.path}`); | 
|  | 97 | +  return result; | 
|  | 98 | +} | 
0 commit comments