Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,55 @@
* found in the LICENSE file at https://angular.dev/license
*/

import { join, relative } from 'node:path';
import { join, relative, sep } from 'node:path';

const bazelBinDirectory = process.env['BAZEL_BINDIR'];
const bazelExecRoot = process.env['JS_BINARY__EXECROOT'];
const execRootMarker = `${sep}execroot${sep}`;

/**
* Sandboxed actions run in a copy of the execroot whose absolute path differs from
* `JS_BINARY__EXECROOT`, so the effective execroot is derived from the working directory.
*/
function effectiveExecRoot(): string | undefined {
const cwd = process.cwd();
const markerIndex = cwd.indexOf(execRootMarker);
if (markerIndex === -1) {
return bazelExecRoot;
}

const workspaceEnd = cwd.indexOf(sep, markerIndex + execRootMarker.length);

return workspaceEnd === -1 ? cwd : cwd.slice(0, workspaceEnd);
}

export function rewriteForBazel(path: string): string {
if (!bazelBinDirectory || !bazelExecRoot) {
return path;
}

const fromExecRoot = relative(bazelExecRoot, path);
const execRoot = effectiveExecRoot() ?? bazelExecRoot;

const fromExecRoot = relative(execRoot, path);
if (!fromExecRoot.startsWith('..')) {
return path;
}

const fromBinDirectory = relative(bazelBinDirectory, path);
if (fromBinDirectory.startsWith('..')) {
return path;
if (!fromBinDirectory.startsWith('..')) {
return join(execRoot, fromBinDirectory);
}

// A path that realpath resolved out of a symlink-staged sandbox into the
// unsandboxed execroot: re-anchor its execroot-relative tail.
const markerIndex = path.indexOf(execRootMarker);
if (markerIndex !== -1) {
const tail = path.slice(markerIndex + execRootMarker.length);
const workspaceEnd = tail.indexOf(sep);
if (workspaceEnd !== -1) {
return join(execRoot, tail.slice(workspaceEnd + 1));
}
}

return join(bazelExecRoot, fromBinDirectory);
return path;
}
Comment on lines 31 to 60

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

On Windows, tools like TypeScript and Esbuild often normalize internal paths to use forward slashes (/), whereas Node's process.cwd() and sep will use backward slashes (\\). This mismatch can cause path.indexOf(execRootMarker) and tail.indexOf(sep) to fail to find matches, preventing path rewriting on Windows.

To ensure robustness across platforms, we should normalize the input path, execRoot, and bazelBinDirectory to use the platform-native path separator (sep) before performing relative path calculations and string searches.

export function rewriteForBazel(path: string): string {
  if (!bazelBinDirectory || !bazelExecRoot) {
    return path;
  }

  const normalizedPath = sep === '\\' ? path.replace(/\//g, '\\') : path;
  const rawExecRoot = effectiveExecRoot() ?? bazelExecRoot;
  const execRoot = sep === '\\' ? rawExecRoot.replace(/\//g, '\\') : rawExecRoot;
  const binDir = sep === '\\' ? bazelBinDirectory.replace(/\//g, '\\') : bazelBinDirectory;

  const fromExecRoot = relative(execRoot, normalizedPath);
  if (!fromExecRoot.startsWith('..')) {
    return path;
  }

  const fromBinDirectory = relative(binDir, normalizedPath);
  if (!fromBinDirectory.startsWith('..')) {
    return join(execRoot, fromBinDirectory);
  }

  // A path that realpath resolved out of a symlink-staged sandbox into the
  // unsandboxed execroot: re-anchor its execroot-relative tail.
  const markerIndex = normalizedPath.indexOf(execRootMarker);
  if (markerIndex !== -1) {
    const tail = normalizedPath.slice(markerIndex + execRootMarker.length);
    const workspaceEnd = tail.indexOf(sep);
    if (workspaceEnd !== -1) {
      return join(execRoot, tail.slice(workspaceEnd + 1));
    }
  }

  return path;
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only call site wraps the argument in path.normalize() ( in compiler-plugin.ts), which on win32 converts forward slashes to backslashes, so the sep-based searches always see native separators. The new branch also only fires under Bazel's symlink-staging local sandboxes (darwin-sandbox/linux-sandbox), which don't exist on Windows.

If the marker ever missed anyway, the function falls through to the pre-existing behavior rather than regressing.

Let me know if this is insufficient and/or warrants more documentation in the code.

Loading