1b939fa09SCedric van Puttenimport { sync as findUpSync } from 'find-up';
28d3f3824SCedric van Puttenimport findYarnOrNpmWorkspaceRootUnsafe from 'find-yarn-workspace-root';
38d3f3824SCedric van Puttenimport fs from 'fs';
48d3f3824SCedric van Puttenimport yaml from 'js-yaml';
58d3f3824SCedric van Puttenimport micromatch from 'micromatch';
6b939fa09SCedric van Puttenimport path from 'path';
7b939fa09SCedric van Putten
8b939fa09SCedric van Puttenexport const NPM_LOCK_FILE = 'package-lock.json';
9b939fa09SCedric van Puttenexport const YARN_LOCK_FILE = 'yarn.lock';
10b939fa09SCedric van Puttenexport const PNPM_LOCK_FILE = 'pnpm-lock.yaml';
11b939fa09SCedric van Puttenexport const PNPM_WORKSPACE_FILE = 'pnpm-workspace.yaml';
12*6a725f6fSColin McDonnellexport const BUN_LOCK_FILE = 'bun.lockb';
13b939fa09SCedric van Putten
148d3f3824SCedric van Putten/** Wraps `find-yarn-workspace-root` and guards against having an empty `package.json` file in an upper directory. */
158d3f3824SCedric van Puttenexport function findYarnOrNpmWorkspaceRoot(projectRoot: string): string | null {
16b939fa09SCedric van Putten  try {
178d3f3824SCedric van Putten    return findYarnOrNpmWorkspaceRootUnsafe(projectRoot);
18b939fa09SCedric van Putten  } catch (error: any) {
19b939fa09SCedric van Putten    if (error.message.includes('Unexpected end of JSON input')) {
20b939fa09SCedric van Putten      return null;
21b939fa09SCedric van Putten    }
22b939fa09SCedric van Putten    throw error;
23b939fa09SCedric van Putten  }
24b939fa09SCedric van Putten}
25b939fa09SCedric van Putten
26b939fa09SCedric van Putten/**
278d3f3824SCedric van Putten * Find the `pnpm-workspace.yaml` file that represents the root of the monorepo.
288d3f3824SCedric van Putten * This is a synchronous function based on the original async library.
298d3f3824SCedric van Putten * @see https://github.com/pnpm/pnpm/blob/main/packages/find-workspace-dir/src/index.ts
30b939fa09SCedric van Putten */
318d3f3824SCedric van Puttenexport function findPnpmWorkspaceRoot(projectRoot: string): string | null {
328d3f3824SCedric van Putten  const workspaceEnvName = 'NPM_CONFIG_WORKSPACE_DIR';
338d3f3824SCedric van Putten  const workspaceEnvValue =
348d3f3824SCedric van Putten    process.env[workspaceEnvName] ?? process.env[workspaceEnvName.toLowerCase()];
35b939fa09SCedric van Putten
368d3f3824SCedric van Putten  const workspaceFile = workspaceEnvValue
378d3f3824SCedric van Putten    ? path.join(workspaceEnvValue, PNPM_WORKSPACE_FILE)
388d3f3824SCedric van Putten    : findUpSync(PNPM_WORKSPACE_FILE, { cwd: projectRoot });
39b939fa09SCedric van Putten
408d3f3824SCedric van Putten  if (!workspaceFile || !fs.existsSync(workspaceFile)) {
41b939fa09SCedric van Putten    return null;
42b939fa09SCedric van Putten  }
43b939fa09SCedric van Putten
448d3f3824SCedric van Putten  try {
458d3f3824SCedric van Putten    // See: https://pnpm.io/pnpm-workspace_yaml
468d3f3824SCedric van Putten    const { packages: workspaces } = yaml.load(fs.readFileSync(workspaceFile, 'utf8'));
478d3f3824SCedric van Putten    // See: https://github.com/square/find-yarn-workspace-root/blob/11f6e31d3fa15a5bb7b7419f0091390e4c16204c/index.js#L26-L33
488d3f3824SCedric van Putten    const workspaceRoot = path.dirname(workspaceFile);
498d3f3824SCedric van Putten    const relativePath = path.relative(workspaceRoot, projectRoot);
50b939fa09SCedric van Putten
518d3f3824SCedric van Putten    if (relativePath === '' || micromatch([relativePath], workspaces).length > 0) {
528d3f3824SCedric van Putten      return workspaceRoot;
53b939fa09SCedric van Putten    }
548d3f3824SCedric van Putten  } catch {
558d3f3824SCedric van Putten    // TODO: implement debug logger?
56b939fa09SCedric van Putten    return null;
57b939fa09SCedric van Putten  }
58b939fa09SCedric van Putten
59b939fa09SCedric van Putten  return null;
60b939fa09SCedric van Putten}
61