-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(config-tools): Added more accurate search for workspace root
- Loading branch information
1 parent
88f4feb
commit c418b08
Showing
11 changed files
with
293 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
import { locatePath, locatePathSync } from "locate-path"; | ||
import path from "path"; | ||
import { fileURLToPath } from "url"; | ||
|
||
export interface FindUpOptions { | ||
cwd?: string; | ||
type: "file" | "directory"; | ||
stopAt?: string; | ||
limit: number; | ||
} | ||
|
||
export const findUpStop = Symbol("findUpStop"); | ||
|
||
function toPath(urlOrPath) { | ||
return urlOrPath instanceof URL ? fileURLToPath(urlOrPath) : urlOrPath; | ||
} | ||
|
||
export async function findUpMultiple( | ||
names: | ||
| string | ||
| string[] | ||
| ((cwd: string) => string) | ||
| ((cwd: string) => string)[], | ||
options: FindUpOptions = { limit: Number.POSITIVE_INFINITY, type: "file" } | ||
) { | ||
let directory = path.resolve(toPath(options.cwd) ?? ""); | ||
const { root } = path.parse(directory); | ||
const stopAt = path.resolve(directory, toPath(options.stopAt ?? root)); | ||
const limit = options.limit ?? Number.POSITIVE_INFINITY; | ||
|
||
if (typeof names === "function") { | ||
const foundPath = names(options.cwd); | ||
|
||
return locatePathSync([foundPath], { ...options, cwd: directory }); | ||
} | ||
|
||
const runNameMatcher = async (name: string | ((cwd: string) => string)) => { | ||
const paths = [name].flat(); | ||
|
||
const runMatcher = async locateOptions => { | ||
if (typeof name !== "function") { | ||
return locatePath(paths as string[], locateOptions); | ||
} | ||
|
||
const foundPath = await name(locateOptions.cwd); | ||
if (typeof foundPath === "string") { | ||
return locatePath([foundPath], locateOptions); | ||
} | ||
|
||
return foundPath; | ||
}; | ||
|
||
const matches = []; | ||
// eslint-disable-next-line no-constant-condition | ||
while (true) { | ||
// eslint-disable-next-line no-await-in-loop | ||
const foundPath = await runMatcher({ ...options, cwd: directory }); | ||
if (foundPath) { | ||
matches.push(path.resolve(directory, foundPath)); | ||
} | ||
|
||
if (directory === stopAt || matches.length >= limit) { | ||
break; | ||
} | ||
|
||
directory = path.dirname(directory); | ||
} | ||
|
||
return matches; | ||
}; | ||
|
||
return ( | ||
await Promise.allSettled( | ||
( | ||
(names && Array.isArray(names) ? names : [names]) as | ||
| string[] | ||
| ((cwd: string) => string)[] | ||
).map((name: string | ((cwd: string) => string)) => runNameMatcher(name)) | ||
) | ||
).flat(); | ||
} | ||
|
||
export function findUpMultipleSync( | ||
names: | ||
| string | ||
| string[] | ||
| ((cwd: string) => string) | ||
| ((cwd: string) => string)[], | ||
options: FindUpOptions = { limit: 1, type: "file" } | ||
) { | ||
let directory = path.resolve(toPath(options.cwd) ?? ""); | ||
const { root } = path.parse(directory); | ||
const stopAt = path.resolve(directory, toPath(options.stopAt) ?? root); | ||
const limit = options.limit ?? Number.POSITIVE_INFINITY; | ||
|
||
if (typeof names === "function") { | ||
const foundPath = names(options.cwd); | ||
|
||
return locatePathSync([foundPath], options); | ||
} | ||
|
||
const runNameMatcher = (name: string | ((cwd: string) => string)) => { | ||
const paths = [name].flat(); | ||
|
||
const runMatcher = locateOptions => { | ||
if (typeof name !== "function") { | ||
return locatePathSync(paths as string[], locateOptions); | ||
} | ||
|
||
const foundPath = name(locateOptions.cwd); | ||
if (typeof foundPath === "string") { | ||
return locatePathSync([foundPath], locateOptions); | ||
} | ||
|
||
return foundPath; | ||
}; | ||
|
||
const matches = []; | ||
// eslint-disable-next-line no-constant-condition | ||
while (true) { | ||
const foundPath = runMatcher({ ...options, cwd: directory }); | ||
if (foundPath) { | ||
matches.push(path.resolve(directory, foundPath)); | ||
} | ||
|
||
if (directory === stopAt || matches.length >= limit) { | ||
break; | ||
} | ||
|
||
directory = path.dirname(directory); | ||
} | ||
|
||
return matches; | ||
}; | ||
|
||
return ( | ||
(names && Array.isArray(names) ? names : [names]) as | ||
| string[] | ||
| ((cwd: string) => string)[] | ||
) | ||
.map((name: string | ((cwd: string) => string)) => runNameMatcher(name)) | ||
.flat(); | ||
} | ||
|
||
export async function findUp( | ||
names: | ||
| string | ||
| string[] | ||
| ((cwd: string) => string) | ||
| ((cwd: string) => string)[], | ||
options: FindUpOptions = { limit: 1, type: "file" } | ||
) { | ||
const matches = await findUpMultiple(names, options); | ||
return matches[0]; | ||
} | ||
|
||
export function findUpSync( | ||
names: | ||
| string | ||
| string[] | ||
| ((cwd: string) => string) | ||
| ((cwd: string) => string)[], | ||
options: FindUpOptions = { limit: 1, type: "file" } | ||
) { | ||
const matches = findUpMultipleSync(names, options); | ||
return matches[0]; | ||
} |
82 changes: 82 additions & 0 deletions
82
packages/config-tools/src/utilities/find-workspace-root.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
/** | ||
* monorepo-root | ||
* Find the monorepo root directory | ||
*/ | ||
|
||
import * as path from "path"; | ||
|
||
import { Either, alt, chain, fromNullable, map, mapLeft } from "fp-ts/Either"; | ||
import { constant, pipe } from "fp-ts/function"; | ||
import { findUp } from "./find-up"; | ||
|
||
export type WorkspaceRootError = { | ||
type: string; | ||
path: string; | ||
}; | ||
|
||
const rootFiles = [ | ||
"lerna.json", | ||
"nx.json", | ||
"turbo.json", | ||
"npm-workspace.json", | ||
"yarn-workspace.json", | ||
"pnpm-workspace.json", | ||
"npm-workspace.yaml", | ||
"yarn-workspace.yaml", | ||
"pnpm-workspace.yaml", | ||
"npm-workspace.yml", | ||
"yarn-workspace.yml", | ||
"pnpm-workspace.yml", | ||
"npm-lock.json", | ||
"yarn-lock.json", | ||
"pnpm-lock.json", | ||
"npm-lock.yaml", | ||
"yarn-lock.yaml", | ||
"pnpm-lock.yaml", | ||
"npm-lock.yml", | ||
"yarn-lock.yml", | ||
"pnpm-lock.yml", | ||
"bun.lockb" | ||
]; | ||
|
||
const formatError = (path: string): WorkspaceRootError => ({ | ||
type: `Cannot find workspace root upwards from known path. Files search list includes: \n${rootFiles.join( | ||
"\n" | ||
)}`, | ||
path | ||
}); | ||
|
||
const searchUp = ( | ||
target: string[], | ||
cwd: string | ||
): Either<WorkspaceRootError, string> => | ||
pipe( | ||
// FIXME: this should be async, or at least offer an async version in addition | ||
findUp(target, { | ||
cwd, | ||
type: "file", | ||
limit: 1 | ||
}), | ||
fromNullable(formatError(cwd)), | ||
map(path.dirname as any) | ||
); | ||
|
||
/** | ||
* Find the monorepo root directory, searching upwards from `path`. | ||
*/ | ||
export function findWorkspaceRoot(pathInsideMonorepo?: string) { | ||
return process.env.STORM_WORKSPACE_ROOT | ||
? process.env.STORM_WORKSPACE_ROOT | ||
: process.env.NX_WORKSPACE_ROOT_PATH | ||
? process.env.NX_WORKSPACE_ROOT_PATH | ||
: pipe( | ||
fromNullable(formatError(""))(pathInsideMonorepo), | ||
chain(from => searchUp(rootFiles, from)), | ||
alt(() => | ||
pipe( | ||
searchUp(rootFiles, process.cwd()), | ||
mapLeft(constant(formatError(process.cwd()))) | ||
) | ||
) | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.