a reactive (signals based) hypermedia web framework (wip)
stormlightlabs.github.io/volt/
hypermedia
frontend
signals
1import { existsSync } from "node:fs";
2import { readFile } from "node:fs/promises";
3import path from "node:path";
4
5/**
6 * Find the monorepo root by walking up the directory tree.
7 *
8 * Looks for pnpm-workspace.yaml with valid workspace packages or a package.json with workspaces defined.
9 */
10export async function findMonorepoRoot(startDir: string = process.cwd()): Promise<string> {
11 let currentDir = startDir;
12 const maxDepth = 10;
13 let depth = 0;
14
15 while (depth < maxDepth) {
16 const workspaceYaml = path.join(currentDir, "pnpm-workspace.yaml");
17 const packageJson = path.join(currentDir, "package.json");
18
19 if (existsSync(workspaceYaml)) {
20 const hasLibPackage = existsSync(path.join(currentDir, "lib", "package.json"));
21 const hasCliPackage = existsSync(path.join(currentDir, "cli", "package.json"));
22
23 if (hasLibPackage || hasCliPackage) {
24 return currentDir;
25 }
26 }
27
28 if (existsSync(packageJson)) {
29 try {
30 const pkgContent = JSON.parse(await readFile(packageJson, "utf8"));
31 if (pkgContent.workspaces || pkgContent.private) {
32 const hasLibPackage = existsSync(path.join(currentDir, "lib", "package.json"));
33 const hasCliPackage = existsSync(path.join(currentDir, "cli", "package.json"));
34
35 if (hasLibPackage || hasCliPackage) {
36 return currentDir;
37 }
38 }
39 } catch {
40 // No-Op: Continue searching
41 }
42 }
43
44 const parentDir = path.dirname(currentDir);
45 if (parentDir === currentDir) {
46 throw new Error("Could not find monorepo root. Make sure you're in the Volt project directory.");
47 }
48
49 currentDir = parentDir;
50 depth++;
51 }
52
53 throw new Error("Could not find monorepo root. Make sure you're in the Volt project directory.");
54}
55
56/**
57 * Get the path to the lib package directory
58 */
59export async function getLibPath(startDir?: string): Promise<string> {
60 const root = await findMonorepoRoot(startDir);
61 return path.join(root, "lib");
62}
63
64/**
65 * Get the path to the docs package directory
66 */
67export async function getDocsPath(startDir?: string): Promise<string> {
68 const root = await findMonorepoRoot(startDir);
69 return path.join(root, "docs");
70}
71
72/**
73 * Get the path to the examples directory
74 */
75export async function getExamplesPath(startDir?: string): Promise<string> {
76 const root = await findMonorepoRoot(startDir);
77 return path.join(root, "examples");
78}
79
80/**
81 * Get the path to the lib source directory
82 */
83export async function getLibSrcPath(startDir?: string): Promise<string> {
84 const libPath = await getLibPath(startDir);
85 return path.join(libPath, "src");
86}
87
88/**
89 * Get the path to the lib test directory
90 */
91export async function getLibTestPath(startDir?: string): Promise<string> {
92 const libPath = await getLibPath(startDir);
93 return path.join(libPath, "test");
94}