-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathconfig.ts
More file actions
104 lines (97 loc) · 3.53 KB
/
Copy pathconfig.ts
File metadata and controls
104 lines (97 loc) · 3.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
// Shared helpers for resolving a repo's .davstack/ config layout.
//
// The design pins config files at `<repo-root>/.davstack/config/<tool>.config.ts`
// so a single set of configs serves a whole monorepo (trace_id correlation
// across services lives or dies by that single-root invariant). The resolver
// here walks up from a starting cwd to find the repo root, then probes the
// canonical path with a backwards-compat fallback for setups that pre-date
// the .davstack/ convention.
import { existsSync, readFileSync } from 'node:fs';
import { dirname, join, resolve } from 'node:path';
import { execFileSync } from 'node:child_process';
const WORKSPACE_MARKERS = [
'pnpm-workspace.yaml',
'turbo.json',
'lerna.json',
];
/**
* Resolve the repo root for a given starting directory.
*
* Order:
* 1. `git rev-parse --show-toplevel` (most reliable; works in any subdir of a git tree)
* 2. Walk up looking for a workspace marker (pnpm-workspace.yaml / turbo.json / lerna.json)
* or a package.json with a `workspaces` field
* 3. Walk up for any package.json
* 4. Fall back to the starting directory
*
* Never throws — returns `start` (or an absolute resolved form of it) if
* nothing matches. Daemons can then decide whether the fallback is safe.
*/
export function findRepoRoot(start: string = process.cwd()): string {
const startAbs = resolve(start);
// 1) git
try {
const out = execFileSync('git', ['rev-parse', '--show-toplevel'], {
cwd: startAbs,
stdio: ['ignore', 'pipe', 'ignore'],
encoding: 'utf8',
});
const trimmed = out.trim();
if (trimmed) return resolve(trimmed);
} catch {
// not a git checkout, or git missing — fall through
}
// 2/3) walk up
let cur = startAbs;
let lastPackageJsonDir: string | undefined;
// hard cap depth to avoid pathological mounts
for (let depth = 0; depth < 64; depth++) {
for (const marker of WORKSPACE_MARKERS) {
if (existsSync(join(cur, marker))) return cur;
}
const pkgPath = join(cur, 'package.json');
if (existsSync(pkgPath)) {
// remember the deepest package.json we've seen; prefer one with workspaces
lastPackageJsonDir = lastPackageJsonDir ?? cur;
try {
// tiny synchronous read is fine for a one-shot init
const text = readFileSync(pkgPath, 'utf8');
const json = JSON.parse(text) as { workspaces?: unknown };
if (json.workspaces) return cur;
} catch {
// ignore parse errors, keep walking
}
}
const parent = dirname(cur);
if (parent === cur) break;
cur = parent;
}
// 4) fallback
return lastPackageJsonDir ?? startAbs;
}
/**
* Locate a tool config file. Returns the resolved absolute path or `null` if
* nothing matches.
*
*
* Resolution order:
* 1. `<repo-root>/.davstack/config/<toolName>.config.ts` — the canonical path
* 2. `<repo-root>/<toolName>.config.ts` — committed-at-root fallback
* 3. `<cwd>/<toolName>.config.ts` — legacy pre-`.davstack/` convention
*/
export function findToolConfig(
toolName: string,
cwd: string = process.cwd(),
): string | null {
const root = findRepoRoot(cwd);
const primary = join(root, '.davstack', 'config', `${toolName}.config.ts`);
if (existsSync(primary)) return primary;
const rootFallback = join(root, `${toolName}.config.ts`);
if (existsSync(rootFallback)) return rootFallback;
const cwdAbs = resolve(cwd);
if (cwdAbs !== root) {
const cwdFallback = join(cwdAbs, `${toolName}.config.ts`);
if (existsSync(cwdFallback)) return cwdFallback;
}
return null;
}