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,30 @@ 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+ return filePath ;
79+ }
80+
5781async function resolveInExecroot ( {
5882 build,
5983 bindir,
6084 execroot,
85+ runfiles,
6186 importPath,
6287 otherOptions,
6388} : ResolveInExecrootOptions ) : Promise < OnResolveResult > {
@@ -85,8 +110,39 @@ async function resolveInExecroot({
85110 `Error: esbuild resolved a path outside of BAZEL_BINDIR (${ bindir } ): ${ result . path } ` ,
86111 ) ;
87112 }
88- // Otherwise remap the bindir-relative path
89- const correctedPath = join ( execroot , result . path . substring ( result . path . indexOf ( bindir ) ) ) ;
113+ // Get the path under the bindir for the file. This allows us to map into
114+ // the execroot or the runfiles directory (if present).
115+ // Example:
116+ // bindir = bazel-out/<arch>/bin
117+ // result.path = <base>/execroot/bazel-out/<arch>/bin/external/repo+/path/file.ts
118+ // binDirRelativePath = external/repo+/path/file.ts
119+ const binDirRelativePath = result . path . substring (
120+ result . path . indexOf ( bindir ) + bindir . length + 1 ,
121+ ) ;
122+ // We usually remap into the bindir. However, when sources are provided
123+ // as `data` (runfiles), they will be in the runfiles root instead. The
124+ // runfiles path is absolute and under the bindir, so we don't need to
125+ // join anything to it. The execroot does not include the bindir, so there
126+ // we add it again after previously removing it from the result path.
127+ const remapBase = runfiles ?? path . join ( execroot , bindir ) ;
128+ // The path relative to the remapBase also differs between runfiles and
129+ // bindir, but only if the file is in an external repository. External
130+ // repositories appear under `external/repo+` in the bindir, whereas they
131+ // are directly under `repo+` in the runfiles tree. This difference needs
132+ // to be accounted for by removing a potential `external/` prefix when
133+ // mapping into runfiles.
134+ const remapBaseRelativePath = runfiles
135+ ? removeExternalPathPrefix ( binDirRelativePath )
136+ : binDirRelativePath ;
137+ // Join the paths back together. The results will look slightly different
138+ // between runfiles and bindir, but this is intentional.
139+ // Source path:
140+ // <bin>/external/repo+/path/file.ts
141+ // Example in bindir:
142+ // <sandbox-bin>/external/repo+/path/file.ts
143+ // Example in runfiles:
144+ // <sandbox-bin>/path/bin.runfiles/repo+/path/file.ts
145+ const correctedPath = join ( remapBase , remapBaseRelativePath ) ;
90146 if ( process . env . JS_BINARY__LOG_DEBUG ) {
91147 // eslint-disable-next-line no-console
92148 console . error (
0 commit comments