Skip to content
Merged
Show file tree
Hide file tree
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
13 changes: 13 additions & 0 deletions .changeset/fix-monorepo-binary-resolution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"varlock": patch
---

Fix binary resolution in monorepos when `cwd` differs from the package root.

When importing `varlock/auto-load` (e.g. from a `playwright.config.ts` in a monorepo sub-package), VS Code and similar tools may set `process.cwd()` to the workspace root rather than the sub-package directory. This caused `execSyncVarlock` to search for the `varlock` binary starting at the workspace root and fail to find it when it was only installed in a sub-package's `node_modules/.bin`.

Two fixes are applied:

1. `execSyncVarlock` now accepts a `callerDir` option. When provided, the binary search walks up from `callerDir` before falling back to `process.cwd()`. `auto-load.ts` passes `import.meta.dirname` so the search always starts from inside the varlock package itself, which is already in the correct sub-package's `node_modules`.

2. The walk-up logic no longer throws immediately when it finds a `node_modules/.bin` directory that does not contain varlock. It now continues walking up, allowing the search to find varlock installed at a higher or lower level of a monorepo.
4 changes: 4 additions & 0 deletions packages/varlock/src/auto-load.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ import { patchGlobalResponse } from './runtime/patch-response';
const execResult = execSyncVarlock('load --format json-full --compact', {
exitOnError: true,
showLogsOnError: true,
// Pass the directory of this module so that in monorepos the binary search
// starts from inside the varlock package (e.g. apps/web/node_modules/varlock)
// rather than from process.cwd(), which may be an unrelated workspace root.
callerDir: import.meta.dirname ?? new URL('.', import.meta.url).pathname,
});
process.env.__VARLOCK_ENV = execResult;

Expand Down
69 changes: 49 additions & 20 deletions packages/varlock/src/lib/exec-sync-varlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,30 @@ const platform = os.platform();
const isWindows = platform.match(/^win/i);


/**
* Walk up the directory tree from startDir looking for a node_modules/.bin/varlock binary.
* Returns the full path to the binary if found, or null if not found.
*/
function findVarlockBin(startDir: string): string | null {
let currentDir = startDir;
while (currentDir) {
const possibleBinPath = path.join(currentDir, 'node_modules', '.bin');
if (fs.existsSync(possibleBinPath)) {
const possibleVarlockPath = path.join(possibleBinPath, isWindows ? 'varlock.exe' : 'varlock');
if (fs.existsSync(possibleVarlockPath)) {
return possibleVarlockPath;
}
// Found a .bin directory but varlock is not in it - keep walking up.
// In a monorepo the root node_modules/.bin may exist without varlock,
// which is installed only in a sub-package.
}
const parentDir = path.dirname(currentDir);
if (parentDir === currentDir) break;
currentDir = parentDir;
}
return null;
}

/**
* small helper to call execSync and call the varlock cli
*
Expand All @@ -22,6 +46,13 @@ export function execSyncVarlock(
opts?: (Parameters<typeof execSyncType>[1] & {
exitOnError?: boolean,
showLogsOnError?: boolean,
/**
* Additional directory to start searching for the varlock binary from.
* Searched before process.cwd(). Pass `import.meta.dirname` from the
* call-site so that in monorepos the binary installed next to the
* importing package is found even when cwd is an unrelated workspace root.
*/
callerDir?: string,
}),
) {
try {
Expand All @@ -41,27 +72,25 @@ export function execSyncVarlock(
}

// if varlock was not found, it either means it is not installed
// or we must find the path to node_modules/.bin ourselves
// so we'll walk up the directory tree looking for it
let currentDir = process.cwd();
while (currentDir) {
const possibleBinPath = path.join(currentDir, 'node_modules', '.bin');
if (fs.existsSync(possibleBinPath)) {
const possibleVarlockPath = path.join(possibleBinPath, 'varlock');
if (fs.existsSync(possibleVarlockPath)) {
const result = execFileSync(possibleVarlockPath, command.split(' '), {
...opts,
stdio: 'pipe',
});
return result.toString();
} else {
throw new Error('Unable to find varlock executable');
}
// or we must find the path to node_modules/.bin ourselves.
// Search from callerDir first (if provided), then from process.cwd().
// This handles monorepo setups where cwd may be an unrelated workspace
// root while varlock is only installed in a sub-package - the callerDir
// supplied by auto-load.ts points inside that sub-package's node_modules.
const searchDirs = [
...(opts?.callerDir ? [opts.callerDir] : []),
process.cwd(),
];

for (const startDir of searchDirs) {
const varlockPath = findVarlockBin(startDir);
if (varlockPath) {
const result = execFileSync(varlockPath, command.split(' '), {
...opts,
stdio: 'pipe',
});
return result.toString();
}
// when we reach the root, it will stop
const parentDir = path.dirname(currentDir);
if (parentDir === currentDir) break;
currentDir = path.dirname(currentDir);
}
throw new Error('Unable to find varlock executable');
} catch (err) {
Expand Down
Loading