@kb-labs/core-workspace 1.7.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +85 -33
- package/dist/index.d.ts +157 -12
- package/dist/index.js +131 -5
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @kb-labs/core-workspace
|
|
2
2
|
|
|
3
|
-
> **Core workspace utilities for KB Labs, including root
|
|
3
|
+
> **Core workspace utilities for KB Labs, including platform/project root resolution.** Provides utilities for locating the KB Labs platform installation and the user's project directory for internal tooling consumption (CLI, services, dev tools).
|
|
4
4
|
|
|
5
5
|
[](LICENSE)
|
|
6
6
|
[](https://nodejs.org/)
|
|
@@ -8,32 +8,65 @@
|
|
|
8
8
|
|
|
9
9
|
## 🎯 Vision & Purpose
|
|
10
10
|
|
|
11
|
-
**@kb-labs/core-workspace** provides
|
|
11
|
+
**@kb-labs/core-workspace** provides root-directory resolution utilities shared by CLI, services, and `kb-dev`.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
KB Labs distinguishes **two** logical roots, which may coincide (dev mode) or differ (installed mode):
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
| Root | What it is | Where it lives |
|
|
16
|
+
| --------------- | ------------------------------------------------------------------------------------------------ | --------------------------------------------------- |
|
|
17
|
+
| **platformRoot** | Directory that contains the installed KB Labs platform code (parent of `node_modules/@kb-labs/*`) | `/opt/kb-labs` (installed), workspace root (dev) |
|
|
18
|
+
| **projectRoot** | Directory that contains the user's `.kb/kb.config.json` and all per-project plugin state | `~/work/my-app` (installed), workspace root (dev) |
|
|
16
19
|
|
|
17
|
-
|
|
20
|
+
**Why two roots?** Plugin discovery needs to scan the platform's `node_modules`, while platform state (`.kb/qa`, `.kb/mind`, `.kb/marketplace.lock`, adapter storage paths like `.kb/database/kb.sqlite`) belongs to the user's project. Until the platform moved out of the workspace, both assumptions resolved to the same `cwd` — and the codebase silently conflated them. This package makes the distinction explicit.
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
- **Responsibilities**: Detect workspace markers, return root path
|
|
21
|
-
- **Dependencies**: `core-sys` for repository utilities
|
|
22
|
+
## 🏗️ Architecture
|
|
22
23
|
|
|
23
|
-
###
|
|
24
|
+
### Core Functions
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
- **Strategy Pattern**: Multiple detection strategies
|
|
26
|
+
#### `resolveRoots(options)` — main entry point
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
Resolves both `platformRoot` and `projectRoot` in a single call. This is what the CLI bootstrap, service bootstrap, and `kb-dev` should use.
|
|
29
29
|
|
|
30
|
+
```typescript
|
|
31
|
+
const { platformRoot, projectRoot, sameLocation, sources } = await resolveRoots({
|
|
32
|
+
moduleUrl: import.meta.url, // from the caller's binary/entry
|
|
33
|
+
startDir: process.cwd(),
|
|
34
|
+
})
|
|
30
35
|
```
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
|
|
37
|
+
- `sameLocation: true` ⇒ dev mode (both roots are the same directory).
|
|
38
|
+
- `sources.platform` / `sources.project` describe how each root was discovered (`'explicit' | 'env' | 'module' | 'marker' | 'config' | 'repo' | 'fallback'`).
|
|
39
|
+
|
|
40
|
+
#### `resolvePlatformRoot(options)`
|
|
41
|
+
|
|
42
|
+
Finds the directory that contains the installed KB Labs platform (parent of `node_modules/@kb-labs/*`).
|
|
43
|
+
|
|
44
|
+
Priority chain:
|
|
45
|
+
|
|
46
|
+
1. Explicit `cwd` option.
|
|
47
|
+
2. `KB_PLATFORM_ROOT` env var (set by the installer wrapper in installed mode).
|
|
48
|
+
3. Walk up from `moduleUrl` (`import.meta.url` of the caller). This walks all the way up and:
|
|
49
|
+
- Prefers the **top-most** `pnpm-workspace.yaml` if one exists (handles KB Labs "workspace of workspaces" dev layout).
|
|
50
|
+
- Falls back to the first directory whose `node_modules` contains a known platform marker (`@kb-labs/cli-bin`, `@kb-labs/core-runtime`) — this is the normal installed-mode path.
|
|
51
|
+
4. Walk up from `startDir` looking for the same markers or `pnpm-workspace.yaml`.
|
|
52
|
+
5. Repository root via `findRepoRoot`.
|
|
53
|
+
6. Fallback to `startDir`.
|
|
54
|
+
|
|
55
|
+
#### `resolveProjectRoot(options)`
|
|
56
|
+
|
|
57
|
+
Finds the user's project root — the directory containing `.kb/kb.config.json`.
|
|
58
|
+
|
|
59
|
+
Priority chain:
|
|
60
|
+
|
|
61
|
+
1. Explicit `cwd` option.
|
|
62
|
+
2. Env vars: `KB_PROJECT_ROOT` (preferred) or legacy `KB_LABS_WORKSPACE_ROOT` / `KB_LABS_REPO_ROOT`.
|
|
63
|
+
3. Nearest `.kb/kb.config.json` ancestor walking up from `startDir`.
|
|
64
|
+
4. Repository root via `findRepoRoot`.
|
|
65
|
+
5. Fallback to `startDir`.
|
|
66
|
+
|
|
67
|
+
#### `resolveWorkspaceRoot(options)` — deprecated alias
|
|
68
|
+
|
|
69
|
+
Kept as an alias of `resolveProjectRoot` for backwards compatibility. Prefer the explicit name.
|
|
37
70
|
|
|
38
71
|
## 🚀 Quick Start
|
|
39
72
|
|
|
@@ -46,32 +79,51 @@ pnpm add @kb-labs/core-workspace
|
|
|
46
79
|
### Basic Usage
|
|
47
80
|
|
|
48
81
|
```typescript
|
|
49
|
-
import {
|
|
82
|
+
import { resolveRoots } from '@kb-labs/core-workspace'
|
|
50
83
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
84
|
+
// In a CLI entrypoint (bin.ts):
|
|
85
|
+
const { platformRoot, projectRoot, sameLocation } = await resolveRoots({
|
|
86
|
+
moduleUrl: import.meta.url,
|
|
87
|
+
startDir: process.cwd(),
|
|
88
|
+
})
|
|
54
89
|
|
|
55
|
-
|
|
56
|
-
console.log('Workspace root:', root);
|
|
57
|
-
}
|
|
90
|
+
console.log({ platformRoot, projectRoot, sameLocation })
|
|
58
91
|
```
|
|
59
92
|
|
|
60
|
-
|
|
93
|
+
### In dev mode (monorepo workspace)
|
|
61
94
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
95
|
+
```text
|
|
96
|
+
platformRoot === projectRoot === <workspace-root>
|
|
97
|
+
sameLocation: true
|
|
98
|
+
sources: { platform: 'module', project: 'config' }
|
|
99
|
+
```
|
|
65
100
|
|
|
66
|
-
|
|
101
|
+
### In installed mode
|
|
67
102
|
|
|
68
|
-
|
|
103
|
+
```text
|
|
104
|
+
platformRoot: /opt/kb-labs (from KB_PLATFORM_ROOT or moduleUrl walk-up)
|
|
105
|
+
projectRoot: /home/user/work/my-project (from .kb/kb.config.json walk-up)
|
|
106
|
+
sameLocation: false
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## ✨ Features
|
|
69
110
|
|
|
70
|
-
|
|
111
|
+
- **Two-root resolution**: clearly distinguishes platform code location from project state location.
|
|
112
|
+
- **Multiple discovery signals**: explicit options, env vars, `import.meta.url` walk-up, marker walk-up, repo root.
|
|
113
|
+
- **Dev/installed parity**: same API works in workspace dev mode and in deployed/installed mode; callers don't need to branch.
|
|
114
|
+
- **Nested-workspace aware**: handles the KB Labs "workspace of workspaces" layout where sub-repos each have their own `pnpm-workspace.yaml`.
|
|
115
|
+
- **Backwards compatible**: legacy `resolveWorkspaceRoot` and `KB_LABS_WORKSPACE_ROOT` / `KB_LABS_REPO_ROOT` env vars still work.
|
|
116
|
+
|
|
117
|
+
## 🔧 Configuration
|
|
71
118
|
|
|
72
119
|
### Environment Variables
|
|
73
120
|
|
|
74
|
-
|
|
121
|
+
| Variable | Purpose |
|
|
122
|
+
| ------------------------- | ----------------------------------------------------------------------------- |
|
|
123
|
+
| `KB_PLATFORM_ROOT` | Override platform root (installer wrapper, CI). |
|
|
124
|
+
| `KB_PROJECT_ROOT` | Override project root. |
|
|
125
|
+
| `KB_LABS_WORKSPACE_ROOT` | Legacy alias for `KB_PROJECT_ROOT` (kept for backwards compatibility). |
|
|
126
|
+
| `KB_LABS_REPO_ROOT` | Legacy alias for `KB_PROJECT_ROOT` (kept for backwards compatibility). |
|
|
75
127
|
|
|
76
128
|
## 🤝 Contributing
|
|
77
129
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,38 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @module @kb-labs/core-workspace/types
|
|
3
|
+
*
|
|
4
|
+
* Types for workspace/platform/project root resolution.
|
|
5
|
+
*
|
|
6
|
+
* KB Labs distinguishes **two** logical roots:
|
|
7
|
+
*
|
|
8
|
+
* - **platformRoot** — the directory that contains the installed KB Labs
|
|
9
|
+
* platform code (i.e. the parent of `node_modules/@kb-labs/*`). This is the
|
|
10
|
+
* place from which plugin discovery should scan `node_modules`, and where
|
|
11
|
+
* cloud/CI deployments may place per-installation platform defaults.
|
|
12
|
+
*
|
|
13
|
+
* - **projectRoot** — the directory that contains the user's `.kb/` folder,
|
|
14
|
+
* including `.kb/kb.config.json` and any runtime state written by plugins
|
|
15
|
+
* (`.kb/qa`, `.kb/mind`, `.kb/release`, `.kb/marketplace.lock`, etc.).
|
|
16
|
+
* This is *where the user is working*.
|
|
17
|
+
*
|
|
18
|
+
* In development (monorepo workspace) mode these two roots typically resolve
|
|
19
|
+
* to the same directory. In installed mode they are different — the platform
|
|
20
|
+
* lives under e.g. `/opt/kb-labs` while the project lives under
|
|
21
|
+
* `~/work/my-app`.
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Minimal filesystem façade used by the resolvers. Allows tests to inject a
|
|
25
|
+
* virtual filesystem without touching `node:fs` directly.
|
|
26
|
+
*/
|
|
27
|
+
interface WorkspaceFs {
|
|
28
|
+
exists(path: string): Promise<boolean>;
|
|
29
|
+
}
|
|
30
|
+
type ProjectRootSource = 'explicit' | 'env' | 'config' | 'repo' | 'fallback';
|
|
31
|
+
interface ProjectRootResolution {
|
|
2
32
|
rootDir: string;
|
|
3
|
-
source:
|
|
33
|
+
source: ProjectRootSource;
|
|
4
34
|
}
|
|
5
|
-
interface
|
|
35
|
+
interface ResolveProjectRootOptions {
|
|
6
36
|
/**
|
|
7
37
|
* Explicit root directory to use (e.g. from CLI flag). Highest priority.
|
|
8
38
|
*/
|
|
@@ -24,20 +54,135 @@ interface ResolveWorkspaceRootOptions {
|
|
|
24
54
|
*/
|
|
25
55
|
verbose?: boolean;
|
|
26
56
|
}
|
|
27
|
-
|
|
28
|
-
|
|
57
|
+
/**
|
|
58
|
+
* @deprecated Use {@link ResolveProjectRootOptions}. Alias kept for backwards
|
|
59
|
+
* compatibility with `resolveWorkspaceRoot`.
|
|
60
|
+
*/
|
|
61
|
+
type ResolveWorkspaceRootOptions = ResolveProjectRootOptions;
|
|
62
|
+
/**
|
|
63
|
+
* @deprecated Use {@link ProjectRootResolution}.
|
|
64
|
+
*/
|
|
65
|
+
type WorkspaceRootResolution = ProjectRootResolution;
|
|
66
|
+
type PlatformRootSource = 'explicit' | 'env' | 'module' | 'marker' | 'repo' | 'fallback';
|
|
67
|
+
interface PlatformRootResolution {
|
|
68
|
+
rootDir: string;
|
|
69
|
+
source: PlatformRootSource;
|
|
70
|
+
}
|
|
71
|
+
interface ResolvePlatformRootOptions {
|
|
72
|
+
/**
|
|
73
|
+
* Explicit root directory to use (e.g. from CLI flag). Highest priority.
|
|
74
|
+
*/
|
|
75
|
+
cwd?: string;
|
|
76
|
+
/**
|
|
77
|
+
* `import.meta.url` of the calling module (typically the CLI entrypoint or
|
|
78
|
+
* a service bootstrap file). Used to locate the installed
|
|
79
|
+
* `node_modules/@kb-labs/*` tree by walking up from the module's physical
|
|
80
|
+
* location. This is the **most reliable** signal in installed mode because
|
|
81
|
+
* it does not depend on process cwd or directory layout assumptions.
|
|
82
|
+
*/
|
|
83
|
+
moduleUrl?: string;
|
|
84
|
+
/**
|
|
85
|
+
* Starting directory for marker-based discovery. Defaults to process.cwd().
|
|
86
|
+
*/
|
|
87
|
+
startDir?: string;
|
|
88
|
+
/**
|
|
89
|
+
* Environment variables map. Defaults to process.env.
|
|
90
|
+
*/
|
|
91
|
+
env?: Record<string, string | undefined>;
|
|
92
|
+
/**
|
|
93
|
+
* Optionally inject custom filesystem helpers for testing.
|
|
94
|
+
*/
|
|
95
|
+
fs?: WorkspaceFs;
|
|
96
|
+
/**
|
|
97
|
+
* When true, includes additional metadata in errors/logs.
|
|
98
|
+
*/
|
|
99
|
+
verbose?: boolean;
|
|
100
|
+
}
|
|
101
|
+
interface ResolveRootsOptions extends ResolvePlatformRootOptions, ResolveProjectRootOptions {
|
|
102
|
+
}
|
|
103
|
+
interface RootsResolution {
|
|
104
|
+
/** Where `node_modules/@kb-labs/*` lives. */
|
|
105
|
+
platformRoot: string;
|
|
106
|
+
/** Where `.kb/kb.config.json` and per-project state live. */
|
|
107
|
+
projectRoot: string;
|
|
108
|
+
/**
|
|
109
|
+
* `true` when both roots resolve to the same physical directory — this is
|
|
110
|
+
* the normal case in the KB Labs monorepo dev mode.
|
|
111
|
+
*/
|
|
112
|
+
sameLocation: boolean;
|
|
113
|
+
/** How each root was resolved (for diagnostics). */
|
|
114
|
+
sources: {
|
|
115
|
+
platform: PlatformRootSource;
|
|
116
|
+
project: ProjectRootSource;
|
|
117
|
+
};
|
|
29
118
|
}
|
|
30
119
|
|
|
31
120
|
/**
|
|
32
|
-
* Resolve the
|
|
121
|
+
* Resolve the project root — the directory that contains the user's
|
|
122
|
+
* `.kb/kb.config.json` and plugin state.
|
|
33
123
|
*
|
|
34
124
|
* Priority order:
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
125
|
+
*
|
|
126
|
+
* 1. Explicit `cwd` option (e.g. CLI flag).
|
|
127
|
+
* 2. Environment variables: `KB_PROJECT_ROOT` (preferred), then the legacy
|
|
128
|
+
* `KB_LABS_WORKSPACE_ROOT` / `KB_LABS_REPO_ROOT`.
|
|
129
|
+
* 3. Nearest `.kb/kb.config.json` ancestor walking up from `startDir`.
|
|
130
|
+
* 4. Repository root discovered via `findRepoRoot` (pnpm workspace, `.git`,
|
|
131
|
+
* `package.json`).
|
|
132
|
+
* 5. Fallback to `startDir` / `process.cwd()`.
|
|
133
|
+
*
|
|
134
|
+
* In monorepo dev mode this typically returns the workspace root. In installed
|
|
135
|
+
* mode it returns the user's project directory.
|
|
136
|
+
*/
|
|
137
|
+
declare function resolveProjectRoot(options?: ResolveProjectRootOptions): Promise<ProjectRootResolution>;
|
|
138
|
+
/**
|
|
139
|
+
* @deprecated Use {@link resolveProjectRoot}. This alias is kept for
|
|
140
|
+
* backwards compatibility; semantics are identical.
|
|
141
|
+
*
|
|
142
|
+
* The name "workspace root" is ambiguous — in KB Labs terminology, what it
|
|
143
|
+
* actually refers to is the project root (where `.kb/kb.config.json` lives),
|
|
144
|
+
* not the KB Labs platform installation. Prefer the explicit name.
|
|
40
145
|
*/
|
|
41
146
|
declare function resolveWorkspaceRoot(options?: ResolveWorkspaceRootOptions): Promise<WorkspaceRootResolution>;
|
|
147
|
+
/**
|
|
148
|
+
* Resolve the platform root — the directory that contains the installed
|
|
149
|
+
* KB Labs platform code (i.e. the parent of `node_modules/@kb-labs/*`).
|
|
150
|
+
*
|
|
151
|
+
* Priority order:
|
|
152
|
+
*
|
|
153
|
+
* 1. Explicit `cwd` option (e.g. CLI flag).
|
|
154
|
+
* 2. Environment variable `KB_PLATFORM_ROOT` (typically set by the installer
|
|
155
|
+
* wrapper script in installed mode).
|
|
156
|
+
* 3. Walk up from `moduleUrl` (e.g. `import.meta.url` of the CLI `bin.ts`),
|
|
157
|
+
* looking for a directory whose `node_modules` contains a known platform
|
|
158
|
+
* package. This is the most reliable signal in installed mode because it
|
|
159
|
+
* does not depend on `process.cwd()`.
|
|
160
|
+
* 4. Walk up from `startDir` looking for either a platform marker or
|
|
161
|
+
* `pnpm-workspace.yaml` (the latter covers monorepo dev mode).
|
|
162
|
+
* 5. Repository root discovered via `findRepoRoot`.
|
|
163
|
+
* 6. Fallback to `startDir` / `process.cwd()`.
|
|
164
|
+
*
|
|
165
|
+
* In monorepo dev mode steps 4 or 5 will typically match the workspace root,
|
|
166
|
+
* so `platformRoot` and `projectRoot` resolve to the same directory.
|
|
167
|
+
*/
|
|
168
|
+
declare function resolvePlatformRoot(options?: ResolvePlatformRootOptions): Promise<PlatformRootResolution>;
|
|
169
|
+
/**
|
|
170
|
+
* Resolve both the platform root and the project root in a single call. This
|
|
171
|
+
* is the main entry point for the CLI, service bootstrap, and `kb-dev`.
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```ts
|
|
175
|
+
* // In bin.ts (the CLI entrypoint):
|
|
176
|
+
* const { platformRoot, projectRoot } = await resolveRoots({
|
|
177
|
+
* moduleUrl: import.meta.url,
|
|
178
|
+
* startDir: process.cwd(),
|
|
179
|
+
* })
|
|
180
|
+
* ```
|
|
181
|
+
*
|
|
182
|
+
* In dev mode `platformRoot === projectRoot` and `sameLocation` is `true`.
|
|
183
|
+
* In installed mode they differ: `platformRoot` points at the platform
|
|
184
|
+
* installation while `projectRoot` points at the user's project directory.
|
|
185
|
+
*/
|
|
186
|
+
declare function resolveRoots(options?: ResolveRootsOptions): Promise<RootsResolution>;
|
|
42
187
|
|
|
43
|
-
export { type ResolveWorkspaceRootOptions, type WorkspaceRootResolution, resolveWorkspaceRoot };
|
|
188
|
+
export { type PlatformRootResolution, type PlatformRootSource, type ProjectRootResolution, type ProjectRootSource, type ResolvePlatformRootOptions, type ResolveProjectRootOptions, type ResolveRootsOptions, type ResolveWorkspaceRootOptions, type RootsResolution, type WorkspaceFs, type WorkspaceRootResolution, resolvePlatformRoot, resolveProjectRoot, resolveRoots, resolveWorkspaceRoot };
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import { promises } from 'fs';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
3
4
|
import { findRepoRoot } from '@kb-labs/core-sys';
|
|
4
5
|
|
|
5
6
|
// src/workspace/root-resolver.ts
|
|
6
7
|
var WORKSPACE_CONFIG_RELATIVE = path.join(".kb", "kb.config.json");
|
|
8
|
+
var PLATFORM_PACKAGE_MARKERS = [
|
|
9
|
+
"@kb-labs/cli-bin",
|
|
10
|
+
"@kb-labs/core-runtime"
|
|
11
|
+
];
|
|
7
12
|
var defaultFs = {
|
|
8
13
|
async exists(target) {
|
|
9
14
|
try {
|
|
@@ -14,11 +19,11 @@ var defaultFs = {
|
|
|
14
19
|
}
|
|
15
20
|
}
|
|
16
21
|
};
|
|
17
|
-
function
|
|
22
|
+
function resolveProjectEnvRoot(env) {
|
|
18
23
|
if (!env) {
|
|
19
24
|
return void 0;
|
|
20
25
|
}
|
|
21
|
-
return env.KB_LABS_WORKSPACE_ROOT ?? env.KB_LABS_REPO_ROOT;
|
|
26
|
+
return env.KB_PROJECT_ROOT ?? env.KB_LABS_WORKSPACE_ROOT ?? env.KB_LABS_REPO_ROOT;
|
|
22
27
|
}
|
|
23
28
|
async function findConfigRoot(startDir, fs) {
|
|
24
29
|
let current = path.resolve(startDir);
|
|
@@ -34,7 +39,7 @@ async function findConfigRoot(startDir, fs) {
|
|
|
34
39
|
current = parent;
|
|
35
40
|
}
|
|
36
41
|
}
|
|
37
|
-
async function
|
|
42
|
+
async function resolveProjectRoot(options = {}) {
|
|
38
43
|
const {
|
|
39
44
|
cwd,
|
|
40
45
|
env = process.env,
|
|
@@ -47,7 +52,7 @@ async function resolveWorkspaceRoot(options = {}) {
|
|
|
47
52
|
source: "explicit"
|
|
48
53
|
};
|
|
49
54
|
}
|
|
50
|
-
const envRoot =
|
|
55
|
+
const envRoot = resolveProjectEnvRoot(env);
|
|
51
56
|
if (envRoot) {
|
|
52
57
|
return {
|
|
53
58
|
rootDir: path.resolve(envRoot),
|
|
@@ -78,7 +83,128 @@ async function resolveWorkspaceRoot(options = {}) {
|
|
|
78
83
|
source: "fallback"
|
|
79
84
|
};
|
|
80
85
|
}
|
|
86
|
+
async function resolveWorkspaceRoot(options = {}) {
|
|
87
|
+
return resolveProjectRoot(options);
|
|
88
|
+
}
|
|
89
|
+
async function hasPlatformMarker(candidate, fs) {
|
|
90
|
+
for (const marker of PLATFORM_PACKAGE_MARKERS) {
|
|
91
|
+
const markerPath = path.join(candidate, "node_modules", marker);
|
|
92
|
+
if (await fs.exists(markerPath)) {
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
async function walkUpForPlatformMarker(startDir, fs) {
|
|
99
|
+
let current = path.resolve(startDir);
|
|
100
|
+
while (true) {
|
|
101
|
+
if (await hasPlatformMarker(current, fs)) {
|
|
102
|
+
return current;
|
|
103
|
+
}
|
|
104
|
+
if (await fs.exists(path.join(current, "pnpm-workspace.yaml"))) {
|
|
105
|
+
return current;
|
|
106
|
+
}
|
|
107
|
+
const parent = path.dirname(current);
|
|
108
|
+
if (parent === current) {
|
|
109
|
+
return void 0;
|
|
110
|
+
}
|
|
111
|
+
current = parent;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async function resolvePlatformRootFromModuleUrl(moduleUrl, fs) {
|
|
115
|
+
let modulePath;
|
|
116
|
+
try {
|
|
117
|
+
modulePath = fileURLToPath(moduleUrl);
|
|
118
|
+
} catch {
|
|
119
|
+
return void 0;
|
|
120
|
+
}
|
|
121
|
+
let current = path.dirname(modulePath);
|
|
122
|
+
let firstMarkerHit;
|
|
123
|
+
let topMostWorkspace;
|
|
124
|
+
while (true) {
|
|
125
|
+
if (!firstMarkerHit && await hasPlatformMarker(current, fs)) {
|
|
126
|
+
firstMarkerHit = current;
|
|
127
|
+
}
|
|
128
|
+
if (await fs.exists(path.join(current, "pnpm-workspace.yaml"))) {
|
|
129
|
+
topMostWorkspace = current;
|
|
130
|
+
}
|
|
131
|
+
const parent = path.dirname(current);
|
|
132
|
+
if (parent === current) {
|
|
133
|
+
return topMostWorkspace ?? firstMarkerHit;
|
|
134
|
+
}
|
|
135
|
+
current = parent;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
async function resolvePlatformRoot(options = {}) {
|
|
139
|
+
const {
|
|
140
|
+
cwd,
|
|
141
|
+
moduleUrl,
|
|
142
|
+
env = process.env,
|
|
143
|
+
fs = defaultFs,
|
|
144
|
+
startDir = process.cwd()
|
|
145
|
+
} = options;
|
|
146
|
+
if (cwd) {
|
|
147
|
+
return {
|
|
148
|
+
rootDir: path.resolve(cwd),
|
|
149
|
+
source: "explicit"
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
const envRoot = env?.KB_PLATFORM_ROOT;
|
|
153
|
+
if (envRoot) {
|
|
154
|
+
return {
|
|
155
|
+
rootDir: path.resolve(envRoot),
|
|
156
|
+
source: "env"
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
if (moduleUrl) {
|
|
160
|
+
const fromModule = await resolvePlatformRootFromModuleUrl(moduleUrl, fs);
|
|
161
|
+
if (fromModule) {
|
|
162
|
+
return {
|
|
163
|
+
rootDir: fromModule,
|
|
164
|
+
source: "module"
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const fromMarker = await walkUpForPlatformMarker(startDir, fs);
|
|
169
|
+
if (fromMarker) {
|
|
170
|
+
return {
|
|
171
|
+
rootDir: fromMarker,
|
|
172
|
+
source: "marker"
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
try {
|
|
176
|
+
const repoRoot = await findRepoRoot(startDir);
|
|
177
|
+
const resolvedRepo = path.resolve(repoRoot);
|
|
178
|
+
const fsRoot = path.parse(resolvedRepo).root;
|
|
179
|
+
if (resolvedRepo !== fsRoot) {
|
|
180
|
+
return {
|
|
181
|
+
rootDir: resolvedRepo,
|
|
182
|
+
source: "repo"
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
} catch {
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
rootDir: path.resolve(startDir),
|
|
189
|
+
source: "fallback"
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
async function resolveRoots(options = {}) {
|
|
193
|
+
const [platform, project] = await Promise.all([
|
|
194
|
+
resolvePlatformRoot(options),
|
|
195
|
+
resolveProjectRoot(options)
|
|
196
|
+
]);
|
|
197
|
+
return {
|
|
198
|
+
platformRoot: platform.rootDir,
|
|
199
|
+
projectRoot: project.rootDir,
|
|
200
|
+
sameLocation: path.resolve(platform.rootDir) === path.resolve(project.rootDir),
|
|
201
|
+
sources: {
|
|
202
|
+
platform: platform.source,
|
|
203
|
+
project: project.source
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
}
|
|
81
207
|
|
|
82
|
-
export { resolveWorkspaceRoot };
|
|
208
|
+
export { resolvePlatformRoot, resolveProjectRoot, resolveRoots, resolveWorkspaceRoot };
|
|
83
209
|
//# sourceMappingURL=index.js.map
|
|
84
210
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/workspace/root-resolver.ts"],"names":["fsp"],"mappings":";;;;;AAWA,IAAM,yBAAA,GAA4B,IAAA,CAAK,IAAA,CAAK,KAAA,EAAO,gBAAgB,CAAA;AAEnE,IAAM,SAAA,GAAyB;AAAA,EAC7B,MAAM,OAAO,MAAA,EAAkC;AAC7C,IAAA,IAAI;AACF,MAAA,MAAMA,QAAA,CAAI,OAAO,MAAM,CAAA;AACvB,MAAA,OAAO,IAAA;AAAA,IACT,CAAA,CAAA,MAAQ;AACN,MAAA,OAAO,KAAA;AAAA,IACT;AAAA,EACF;AACF,CAAA;AAEA,SAAS,eACP,GAAA,EACoB;AACpB,EAAA,IAAI,CAAC,GAAA,EAAK;AAAC,IAAA,OAAO,MAAA;AAAA,EAAS;AAC3B,EAAA,OAAO,GAAA,CAAI,0BAA0B,GAAA,CAAI,iBAAA;AAC3C;AAEA,eAAe,cAAA,CACb,UACA,EAAA,EAC6B;AAC7B,EAAA,IAAI,OAAA,GAAU,IAAA,CAAK,OAAA,CAAQ,QAAQ,CAAA;AAEnC,EAAA,OAAO,IAAA,EAAM;AACX,IAAA,MAAM,UAAA,GAAa,IAAA,CAAK,IAAA,CAAK,OAAA,EAAS,yBAAyB,CAAA;AAE/D,IAAA,IAAI,MAAM,EAAA,CAAG,MAAA,CAAO,UAAU,CAAA,EAAG;AAC/B,MAAA,OAAO,OAAA;AAAA,IACT;AAEA,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,OAAA,CAAQ,OAAO,CAAA;AACnC,IAAA,IAAI,WAAW,OAAA,EAAS;AACtB,MAAA,OAAO,MAAA;AAAA,IACT;AAEA,IAAA,OAAA,GAAU,MAAA;AAAA,EACZ;AACF;AAYA,eAAsB,oBAAA,CACpB,OAAA,GAAuC,EAAC,EACN;AAClC,EAAA,MAAM;AAAA,IACJ,GAAA;AAAA,IACA,MAAM,OAAA,CAAQ,GAAA;AAAA,IACd,EAAA,GAAK,SAAA;AAAA,IACL,QAAA,GAAW,QAAQ,GAAA;AAAI,GACzB,GAAI,OAAA;AAEJ,EAAA,IAAI,GAAA,EAAK;AACP,IAAA,OAAO;AAAA,MACL,OAAA,EAAS,IAAA,CAAK,OAAA,CAAQ,GAAG,CAAA;AAAA,MACzB,MAAA,EAAQ;AAAA,KACV;AAAA,EACF;AAEA,EAAA,MAAM,OAAA,GAAU,eAAe,GAAG,CAAA;AAClC,EAAA,IAAI,OAAA,EAAS;AACX,IAAA,OAAO;AAAA,MACL,OAAA,EAAS,IAAA,CAAK,OAAA,CAAQ,OAAO,CAAA;AAAA,MAC7B,MAAA,EAAQ;AAAA,KACV;AAAA,EACF;AAEA,EAAA,MAAM,UAAA,GAAa,MAAM,cAAA,CAAe,QAAA,EAAU,EAAE,CAAA;AACpD,EAAA,IAAI,UAAA,EAAY;AACd,IAAA,OAAO;AAAA,MACL,OAAA,EAAS,UAAA;AAAA,MACT,MAAA,EAAQ;AAAA,KACV;AAAA,EACF;AAEA,EAAA,IAAI;AACF,IAAA,MAAM,QAAA,GAAW,MAAM,YAAA,CAAa,QAAQ,CAAA;AAC5C,IAAA,MAAM,YAAA,GAAe,IAAA,CAAK,OAAA,CAAQ,QAAQ,CAAA;AAC1C,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,KAAA,CAAM,YAAY,CAAA,CAAE,IAAA;AAExC,IAAA,IAAI,iBAAiB,MAAA,EAAQ;AAC3B,MAAA,OAAO;AAAA,QACL,OAAA,EAAS,YAAA;AAAA,QACT,MAAA,EAAQ;AAAA,OACV;AAAA,IACF;AAAA,EACF,CAAA,CAAA,MAAQ;AAAA,EAER;AAEA,EAAA,OAAO;AAAA,IACL,OAAA,EAAS,IAAA,CAAK,OAAA,CAAQ,QAAQ,CAAA;AAAA,IAC9B,MAAA,EAAQ;AAAA,GACV;AACF","file":"index.js","sourcesContent":["import path from 'node:path'\nimport { promises as fsp } from 'node:fs'\n\nimport { findRepoRoot } from '@kb-labs/core-sys'\n\nimport type {\n ResolveWorkspaceRootOptions,\n WorkspaceFs,\n WorkspaceRootResolution,\n} from './types'\n\nconst WORKSPACE_CONFIG_RELATIVE = path.join('.kb', 'kb.config.json')\n\nconst defaultFs: WorkspaceFs = {\n async exists(target: string): Promise<boolean> {\n try {\n await fsp.access(target)\n return true\n } catch {\n return false\n }\n },\n}\n\nfunction resolveEnvRoot(\n env: Record<string, string | undefined> | undefined,\n): string | undefined {\n if (!env) {return undefined}\n return env.KB_LABS_WORKSPACE_ROOT ?? env.KB_LABS_REPO_ROOT\n}\n\nasync function findConfigRoot(\n startDir: string,\n fs: WorkspaceFs,\n): Promise<string | undefined> {\n let current = path.resolve(startDir)\n\n while (true) {\n const configPath = path.join(current, WORKSPACE_CONFIG_RELATIVE)\n // eslint-disable-next-line no-await-in-loop -- Searching for workspace root: must check each directory sequentially\n if (await fs.exists(configPath)) {\n return current\n }\n\n const parent = path.dirname(current)\n if (parent === current) {\n return undefined\n }\n\n current = parent\n }\n}\n\n/**\n * Resolve the workspace root for kb-labs tooling.\n *\n * Priority order:\n * 1. Explicit `cwd` option (e.g. CLI flag)\n * 2. Environment variables (`KB_LABS_WORKSPACE_ROOT`, `KB_LABS_REPO_ROOT`)\n * 3. Nearest `.kb/kb-labs.config.json` ancestor\n * 4. Repository root discovered via `findRepoRoot`\n * 5. Fallback to provided `startDir` / process cwd\n */\nexport async function resolveWorkspaceRoot(\n options: ResolveWorkspaceRootOptions = {},\n): Promise<WorkspaceRootResolution> {\n const {\n cwd,\n env = process.env,\n fs = defaultFs,\n startDir = process.cwd(),\n } = options\n\n if (cwd) {\n return {\n rootDir: path.resolve(cwd),\n source: 'explicit',\n }\n }\n\n const envRoot = resolveEnvRoot(env)\n if (envRoot) {\n return {\n rootDir: path.resolve(envRoot),\n source: 'env',\n }\n }\n\n const configRoot = await findConfigRoot(startDir, fs)\n if (configRoot) {\n return {\n rootDir: configRoot,\n source: 'config',\n }\n }\n\n try {\n const repoRoot = await findRepoRoot(startDir)\n const resolvedRepo = path.resolve(repoRoot)\n const fsRoot = path.parse(resolvedRepo).root\n\n if (resolvedRepo !== fsRoot) {\n return {\n rootDir: resolvedRepo,\n source: 'repo',\n }\n }\n } catch {\n // swallow and fall through to fallback\n }\n\n return {\n rootDir: path.resolve(startDir),\n source: 'fallback',\n }\n}\n\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/workspace/root-resolver.ts"],"names":["fsp"],"mappings":"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","file":"index.js","sourcesContent":["import path from 'node:path'\nimport { promises as fsp } from 'node:fs'\nimport { fileURLToPath } from 'node:url'\n\nimport { findRepoRoot } from '@kb-labs/core-sys'\n\nimport type {\n PlatformRootResolution,\n ProjectRootResolution,\n ResolvePlatformRootOptions,\n ResolveProjectRootOptions,\n ResolveRootsOptions,\n ResolveWorkspaceRootOptions,\n RootsResolution,\n WorkspaceFs,\n WorkspaceRootResolution,\n} from './types'\n\nconst WORKSPACE_CONFIG_RELATIVE = path.join('.kb', 'kb.config.json')\n\n/**\n * Package markers used to recognise an installed KB Labs platform. Any one of\n * these, when present as `<root>/node_modules/<marker>`, indicates that\n * `<root>` is a platform installation.\n */\nconst PLATFORM_PACKAGE_MARKERS = [\n '@kb-labs/cli-bin',\n '@kb-labs/core-runtime',\n] as const\n\nconst defaultFs: WorkspaceFs = {\n async exists(target: string): Promise<boolean> {\n try {\n await fsp.access(target)\n return true\n } catch {\n return false\n }\n },\n}\n\n// ──────────────────────────────────────────────────────────────────────────\n// Project root (previously \"workspace root\")\n// ──────────────────────────────────────────────────────────────────────────\n\nfunction resolveProjectEnvRoot(\n env: Record<string, string | undefined> | undefined,\n): string | undefined {\n if (!env) {\n return undefined\n }\n return (\n env.KB_PROJECT_ROOT ??\n env.KB_LABS_WORKSPACE_ROOT ??\n env.KB_LABS_REPO_ROOT\n )\n}\n\nasync function findConfigRoot(\n startDir: string,\n fs: WorkspaceFs,\n): Promise<string | undefined> {\n let current = path.resolve(startDir)\n\n while (true) {\n const configPath = path.join(current, WORKSPACE_CONFIG_RELATIVE)\n // eslint-disable-next-line no-await-in-loop -- Searching for project root: must check each directory sequentially\n if (await fs.exists(configPath)) {\n return current\n }\n\n const parent = path.dirname(current)\n if (parent === current) {\n return undefined\n }\n\n current = parent\n }\n}\n\n/**\n * Resolve the project root — the directory that contains the user's\n * `.kb/kb.config.json` and plugin state.\n *\n * Priority order:\n *\n * 1. Explicit `cwd` option (e.g. CLI flag).\n * 2. Environment variables: `KB_PROJECT_ROOT` (preferred), then the legacy\n * `KB_LABS_WORKSPACE_ROOT` / `KB_LABS_REPO_ROOT`.\n * 3. Nearest `.kb/kb.config.json` ancestor walking up from `startDir`.\n * 4. Repository root discovered via `findRepoRoot` (pnpm workspace, `.git`,\n * `package.json`).\n * 5. Fallback to `startDir` / `process.cwd()`.\n *\n * In monorepo dev mode this typically returns the workspace root. In installed\n * mode it returns the user's project directory.\n */\nexport async function resolveProjectRoot(\n options: ResolveProjectRootOptions = {},\n): Promise<ProjectRootResolution> {\n const {\n cwd,\n env = process.env,\n fs = defaultFs,\n startDir = process.cwd(),\n } = options\n\n if (cwd) {\n return {\n rootDir: path.resolve(cwd),\n source: 'explicit',\n }\n }\n\n const envRoot = resolveProjectEnvRoot(env)\n if (envRoot) {\n return {\n rootDir: path.resolve(envRoot),\n source: 'env',\n }\n }\n\n const configRoot = await findConfigRoot(startDir, fs)\n if (configRoot) {\n return {\n rootDir: configRoot,\n source: 'config',\n }\n }\n\n try {\n const repoRoot = await findRepoRoot(startDir)\n const resolvedRepo = path.resolve(repoRoot)\n const fsRoot = path.parse(resolvedRepo).root\n\n if (resolvedRepo !== fsRoot) {\n return {\n rootDir: resolvedRepo,\n source: 'repo',\n }\n }\n } catch {\n // swallow and fall through to fallback\n }\n\n return {\n rootDir: path.resolve(startDir),\n source: 'fallback',\n }\n}\n\n/**\n * @deprecated Use {@link resolveProjectRoot}. This alias is kept for\n * backwards compatibility; semantics are identical.\n *\n * The name \"workspace root\" is ambiguous — in KB Labs terminology, what it\n * actually refers to is the project root (where `.kb/kb.config.json` lives),\n * not the KB Labs platform installation. Prefer the explicit name.\n */\nexport async function resolveWorkspaceRoot(\n options: ResolveWorkspaceRootOptions = {},\n): Promise<WorkspaceRootResolution> {\n return resolveProjectRoot(options)\n}\n\n// ──────────────────────────────────────────────────────────────────────────\n// Platform root\n// ──────────────────────────────────────────────────────────────────────────\n\n/**\n * Check whether `<candidate>/node_modules/<marker>` exists for any of the\n * known platform package markers.\n */\nasync function hasPlatformMarker(\n candidate: string,\n fs: WorkspaceFs,\n): Promise<boolean> {\n for (const marker of PLATFORM_PACKAGE_MARKERS) {\n const markerPath = path.join(candidate, 'node_modules', marker)\n // eslint-disable-next-line no-await-in-loop -- Sequential probe is intentional\n if (await fs.exists(markerPath)) {\n return true\n }\n }\n return false\n}\n\n/**\n * Walk up from `startDir` looking for either a platform marker\n * (`node_modules/@kb-labs/*`) or a pnpm workspace marker\n * (`pnpm-workspace.yaml`). Returns the first match.\n */\nasync function walkUpForPlatformMarker(\n startDir: string,\n fs: WorkspaceFs,\n): Promise<string | undefined> {\n let current = path.resolve(startDir)\n\n while (true) {\n // eslint-disable-next-line no-await-in-loop -- Sequential walk is intentional\n if (await hasPlatformMarker(current, fs)) {\n return current\n }\n\n // eslint-disable-next-line no-await-in-loop -- Sequential walk is intentional\n if (await fs.exists(path.join(current, 'pnpm-workspace.yaml'))) {\n return current\n }\n\n const parent = path.dirname(current)\n if (parent === current) {\n return undefined\n }\n current = parent\n }\n}\n\n/**\n * Given a `file://` URL (typically `import.meta.url` from a CLI entry or\n * service bootstrap), walk up through its directory ancestors to locate the\n * platform installation root.\n *\n * Two signals are collected on the way up:\n *\n * 1. **First platform-package marker hit** — a directory whose `node_modules`\n * contains `@kb-labs/cli-bin` (or another known platform marker). In\n * installed mode this is the platform installation directory.\n *\n * 2. **Top-most `pnpm-workspace.yaml`** — we keep walking past inner workspace\n * markers and record the *outermost* one. This is important in the\n * KB Labs \"workspace of workspaces\" dev layout, where individual sub-repos\n * (`platform/kb-labs-cli`, `plugins/kb-labs-*`, etc.) each have their own\n * `pnpm-workspace.yaml` while the true monorepo root lives at the very top.\n *\n * Precedence after walking: the outermost `pnpm-workspace.yaml` wins if one\n * exists (dev mode); otherwise we return the first marker hit (installed\n * mode). This way a naive walk-up can never stop prematurely at a nested\n * workspace or at a package with its own hoisting symlink.\n */\nasync function resolvePlatformRootFromModuleUrl(\n moduleUrl: string,\n fs: WorkspaceFs,\n): Promise<string | undefined> {\n let modulePath: string\n try {\n modulePath = fileURLToPath(moduleUrl)\n } catch {\n return undefined\n }\n\n let current = path.dirname(modulePath)\n let firstMarkerHit: string | undefined\n let topMostWorkspace: string | undefined\n\n while (true) {\n // eslint-disable-next-line no-await-in-loop -- Sequential walk is intentional\n if (!firstMarkerHit && (await hasPlatformMarker(current, fs))) {\n firstMarkerHit = current\n }\n\n // Keep overwriting: we want the *outermost* (highest-up) workspace root,\n // not the innermost. This handles the KB Labs nested-workspaces layout.\n // eslint-disable-next-line no-await-in-loop -- Sequential walk is intentional\n if (await fs.exists(path.join(current, 'pnpm-workspace.yaml'))) {\n topMostWorkspace = current\n }\n\n const parent = path.dirname(current)\n if (parent === current) {\n // Reached filesystem root. Prefer top-most workspace (dev mode);\n // otherwise return first marker hit (installed mode).\n return topMostWorkspace ?? firstMarkerHit\n }\n current = parent\n }\n}\n\n/**\n * Resolve the platform root — the directory that contains the installed\n * KB Labs platform code (i.e. the parent of `node_modules/@kb-labs/*`).\n *\n * Priority order:\n *\n * 1. Explicit `cwd` option (e.g. CLI flag).\n * 2. Environment variable `KB_PLATFORM_ROOT` (typically set by the installer\n * wrapper script in installed mode).\n * 3. Walk up from `moduleUrl` (e.g. `import.meta.url` of the CLI `bin.ts`),\n * looking for a directory whose `node_modules` contains a known platform\n * package. This is the most reliable signal in installed mode because it\n * does not depend on `process.cwd()`.\n * 4. Walk up from `startDir` looking for either a platform marker or\n * `pnpm-workspace.yaml` (the latter covers monorepo dev mode).\n * 5. Repository root discovered via `findRepoRoot`.\n * 6. Fallback to `startDir` / `process.cwd()`.\n *\n * In monorepo dev mode steps 4 or 5 will typically match the workspace root,\n * so `platformRoot` and `projectRoot` resolve to the same directory.\n */\nexport async function resolvePlatformRoot(\n options: ResolvePlatformRootOptions = {},\n): Promise<PlatformRootResolution> {\n const {\n cwd,\n moduleUrl,\n env = process.env,\n fs = defaultFs,\n startDir = process.cwd(),\n } = options\n\n if (cwd) {\n return {\n rootDir: path.resolve(cwd),\n source: 'explicit',\n }\n }\n\n const envRoot = env?.KB_PLATFORM_ROOT\n if (envRoot) {\n return {\n rootDir: path.resolve(envRoot),\n source: 'env',\n }\n }\n\n if (moduleUrl) {\n const fromModule = await resolvePlatformRootFromModuleUrl(moduleUrl, fs)\n if (fromModule) {\n return {\n rootDir: fromModule,\n source: 'module',\n }\n }\n }\n\n const fromMarker = await walkUpForPlatformMarker(startDir, fs)\n if (fromMarker) {\n return {\n rootDir: fromMarker,\n source: 'marker',\n }\n }\n\n try {\n const repoRoot = await findRepoRoot(startDir)\n const resolvedRepo = path.resolve(repoRoot)\n const fsRoot = path.parse(resolvedRepo).root\n\n if (resolvedRepo !== fsRoot) {\n return {\n rootDir: resolvedRepo,\n source: 'repo',\n }\n }\n } catch {\n // swallow and fall through to fallback\n }\n\n return {\n rootDir: path.resolve(startDir),\n source: 'fallback',\n }\n}\n\n// ──────────────────────────────────────────────────────────────────────────\n// Composite resolver\n// ──────────────────────────────────────────────────────────────────────────\n\n/**\n * Resolve both the platform root and the project root in a single call. This\n * is the main entry point for the CLI, service bootstrap, and `kb-dev`.\n *\n * @example\n * ```ts\n * // In bin.ts (the CLI entrypoint):\n * const { platformRoot, projectRoot } = await resolveRoots({\n * moduleUrl: import.meta.url,\n * startDir: process.cwd(),\n * })\n * ```\n *\n * In dev mode `platformRoot === projectRoot` and `sameLocation` is `true`.\n * In installed mode they differ: `platformRoot` points at the platform\n * installation while `projectRoot` points at the user's project directory.\n */\nexport async function resolveRoots(\n options: ResolveRootsOptions = {},\n): Promise<RootsResolution> {\n const [platform, project] = await Promise.all([\n resolvePlatformRoot(options),\n resolveProjectRoot(options),\n ])\n\n return {\n platformRoot: platform.rootDir,\n projectRoot: project.rootDir,\n sameLocation:\n path.resolve(platform.rootDir) === path.resolve(project.rootDir),\n sources: {\n platform: platform.source,\n project: project.source,\n },\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kb-labs/core-workspace",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "Core workspace utilities for KB Labs, including root directory resolution and workspace detection",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"test:watch": "vitest"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@kb-labs/core-sys": "^1.
|
|
32
|
+
"@kb-labs/core-sys": "^1.8.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@kb-labs/devkit": "link:../../../../infra/kb-labs-devkit",
|