1212
1313import type { OnResolveResult , Plugin , PluginBuild , ResolveOptions } from 'esbuild' ;
1414import { stat } from 'node:fs/promises' ;
15- import { join } from 'node:path' ;
15+ import path , { join } from 'node:path' ;
1616
1717export interface CreateBazelSandboxPluginOptions {
1818 bindir : string ;
1919 execroot : string ;
20+ runfiles ?: string ;
2021}
2122
2223// 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.
@@ -25,6 +26,7 @@ export interface CreateBazelSandboxPluginOptions {
2526export function createBazelSandboxPlugin ( {
2627 bindir,
2728 execroot,
29+ runfiles,
2830} : CreateBazelSandboxPluginOptions ) : Plugin {
2931 return {
3032 name : 'bazel-sandbox' ,
@@ -40,7 +42,14 @@ export function createBazelSandboxPlugin({
4042 }
4143 otherOptions . pluginData . executedSandboxPlugin = true ;
4244
43- return await resolveInExecroot ( { build, bindir, execroot, importPath, otherOptions } ) ;
45+ return await resolveInExecroot ( {
46+ build,
47+ bindir,
48+ execroot,
49+ runfiles,
50+ importPath,
51+ otherOptions,
52+ } ) ;
4453 } ) ;
4554 } ,
4655 } ;
@@ -50,14 +59,31 @@ interface ResolveInExecrootOptions {
5059 build : PluginBuild ;
5160 bindir : string ;
5261 execroot : string ;
62+ runfiles ?: string ;
5363 importPath : string ;
5464 otherOptions : ResolveOptions ;
5565}
5666
67+ const EXTERNAL_PREFIX = 'external/' ;
68+
69+ function removeExternalPathPrefix ( filePath : string ) : string {
70+ // Normalize to relative path without leading slash.
71+ if ( filePath . startsWith ( '/' ) ) {
72+ filePath = filePath . substring ( 1 ) ;
73+ }
74+ // Remove the EXTERNAL_PREFIX if present.
75+ if ( filePath . startsWith ( EXTERNAL_PREFIX ) ) {
76+ filePath = filePath . substring ( EXTERNAL_PREFIX . length ) ;
77+ }
78+
79+ return filePath ;
80+ }
81+
5782async function resolveInExecroot ( {
5883 build,
5984 bindir,
6085 execroot,
86+ runfiles,
6187 importPath,
6288 otherOptions,
6389} : ResolveInExecrootOptions ) : Promise < OnResolveResult > {
@@ -85,8 +111,39 @@ async function resolveInExecroot({
85111 `Error: esbuild resolved a path outside of BAZEL_BINDIR (${ bindir } ): ${ result . path } ` ,
86112 ) ;
87113 }
88- // Otherwise remap the bindir-relative path
89- const correctedPath = join ( execroot , result . path . substring ( result . path . indexOf ( bindir ) ) ) ;
114+ // Get the path under the bindir for the file. This allows us to map into
115+ // the execroot or the runfiles directory (if present).
116+ // Example:
117+ // bindir = bazel-out/<arch>/bin
118+ // result.path = <base>/execroot/bazel-out/<arch>/bin/external/repo+/path/file.ts
119+ // binDirRelativePath = external/repo+/path/file.ts
120+ const binDirRelativePath = result . path . substring (
121+ result . path . indexOf ( bindir ) + bindir . length + 1 ,
122+ ) ;
123+ // We usually remap into the bindir. However, when sources are provided
124+ // as `data` (runfiles), they will be in the runfiles root instead. The
125+ // runfiles path is absolute and under the bindir, so we don't need to
126+ // join anything to it. The execroot does not include the bindir, so there
127+ // we add it again after previously removing it from the result path.
128+ const remapBase = runfiles ?? path . join ( execroot , bindir ) ;
129+ // The path relative to the remapBase also differs between runfiles and
130+ // bindir, but only if the file is in an external repository. External
131+ // repositories appear under `external/repo+` in the bindir, whereas they
132+ // are directly under `repo+` in the runfiles tree. This difference needs
133+ // to be accounted for by removing a potential `external/` prefix when
134+ // mapping into runfiles.
135+ const remapBaseRelativePath = runfiles
136+ ? removeExternalPathPrefix ( binDirRelativePath )
137+ : binDirRelativePath ;
138+ // Join the paths back together. The results will look slightly different
139+ // between runfiles and bindir, but this is intentional.
140+ // Source path:
141+ // <bin>/external/repo+/path/file.ts
142+ // Example in bindir:
143+ // <sandbox-bin>/external/repo+/path/file.ts
144+ // Example in runfiles:
145+ // <sandbox-bin>/path/bin.runfiles/repo+/path/file.ts
146+ const correctedPath = join ( remapBase , remapBaseRelativePath ) ;
90147 if ( process . env . JS_BINARY__LOG_DEBUG ) {
91148 // eslint-disable-next-line no-console
92149 console . error (
0 commit comments