@kb-labs/core-workspace 1.0.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/LICENSE +2 -0
- package/README.md +82 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.js +84 -0
- package/dist/index.js.map +1 -0
- package/package.json +52 -0
package/LICENSE
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# @kb-labs/core-workspace
|
|
2
|
+
|
|
3
|
+
> **Core workspace utilities for KB Labs, including root directory resolution and workspace detection.** Provides utilities for locating the KB Labs workspace umbrella directory for internal tooling consumption.
|
|
4
|
+
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](https://nodejs.org/)
|
|
7
|
+
[](https://pnpm.io/)
|
|
8
|
+
|
|
9
|
+
## 🎯 Vision & Purpose
|
|
10
|
+
|
|
11
|
+
**@kb-labs/core-workspace** provides workspace utilities for KB Labs internal tooling (CLI, REST, Studio). It resolves the workspace root directory by detecting workspace markers (`.git`, `pnpm-workspace.yaml`, `package.json`) and provides workspace metadata.
|
|
12
|
+
|
|
13
|
+
## 🏗️ Architecture
|
|
14
|
+
|
|
15
|
+
### Core Components
|
|
16
|
+
|
|
17
|
+
#### Workspace Root Resolver
|
|
18
|
+
|
|
19
|
+
- **Purpose**: Resolve workspace root directory
|
|
20
|
+
- **Responsibilities**: Detect workspace markers, return root path
|
|
21
|
+
- **Dependencies**: `core-sys` for repository utilities
|
|
22
|
+
|
|
23
|
+
### Design Patterns
|
|
24
|
+
|
|
25
|
+
- **Utility Pattern**: Pure utility functions
|
|
26
|
+
- **Strategy Pattern**: Multiple detection strategies
|
|
27
|
+
|
|
28
|
+
### Data Flow
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
resolveWorkspaceRoot({ startDir, stopDir })
|
|
32
|
+
│
|
|
33
|
+
├──► Walk up directory tree
|
|
34
|
+
├──► Check for markers (.git, pnpm-workspace.yaml, package.json)
|
|
35
|
+
├──► Return workspace root
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## 🚀 Quick Start
|
|
39
|
+
|
|
40
|
+
### Installation
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pnpm add @kb-labs/core-workspace
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Basic Usage
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
import { resolveWorkspaceRoot } from '@kb-labs/core-workspace';
|
|
50
|
+
|
|
51
|
+
const { root, found } = await resolveWorkspaceRoot({
|
|
52
|
+
startDir: process.cwd()
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
if (found) {
|
|
56
|
+
console.log('Workspace root:', root);
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## ✨ Features
|
|
61
|
+
|
|
62
|
+
- **Multiple Markers**: Detects workspace via `.git`, `pnpm-workspace.yaml`, `package.json`
|
|
63
|
+
- **Flexible Resolution**: Supports start and stop directories
|
|
64
|
+
- **Workspace Metadata**: Provides workspace filesystem information
|
|
65
|
+
|
|
66
|
+
## 🔧 Configuration
|
|
67
|
+
|
|
68
|
+
### Configuration Options
|
|
69
|
+
|
|
70
|
+
No configuration needed (utilities only).
|
|
71
|
+
|
|
72
|
+
### Environment Variables
|
|
73
|
+
|
|
74
|
+
None.
|
|
75
|
+
|
|
76
|
+
## 🤝 Contributing
|
|
77
|
+
|
|
78
|
+
See [CONTRIBUTING.md](../../CONTRIBUTING.md) for development guidelines.
|
|
79
|
+
|
|
80
|
+
## 📄 License
|
|
81
|
+
|
|
82
|
+
KB Public License v1.1 © KB Labs
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
interface WorkspaceRootResolution {
|
|
2
|
+
rootDir: string;
|
|
3
|
+
source: 'explicit' | 'env' | 'config' | 'repo' | 'fallback';
|
|
4
|
+
}
|
|
5
|
+
interface ResolveWorkspaceRootOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Explicit root directory to use (e.g. from CLI flag). Highest priority.
|
|
8
|
+
*/
|
|
9
|
+
cwd?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Starting directory for discovery. Defaults to process.cwd().
|
|
12
|
+
*/
|
|
13
|
+
startDir?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Environment variables map. Defaults to process.env.
|
|
16
|
+
*/
|
|
17
|
+
env?: Record<string, string | undefined>;
|
|
18
|
+
/**
|
|
19
|
+
* Optionally inject custom filesystem helpers for testing.
|
|
20
|
+
*/
|
|
21
|
+
fs?: WorkspaceFs;
|
|
22
|
+
/**
|
|
23
|
+
* When true, includes additional metadata in errors/logs.
|
|
24
|
+
*/
|
|
25
|
+
verbose?: boolean;
|
|
26
|
+
}
|
|
27
|
+
interface WorkspaceFs {
|
|
28
|
+
exists(path: string): Promise<boolean>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Resolve the workspace root for kb-labs tooling.
|
|
33
|
+
*
|
|
34
|
+
* Priority order:
|
|
35
|
+
* 1. Explicit `cwd` option (e.g. CLI flag)
|
|
36
|
+
* 2. Environment variables (`KB_LABS_WORKSPACE_ROOT`, `KB_LABS_REPO_ROOT`)
|
|
37
|
+
* 3. Nearest `.kb/kb-labs.config.json` ancestor
|
|
38
|
+
* 4. Repository root discovered via `findRepoRoot`
|
|
39
|
+
* 5. Fallback to provided `startDir` / process cwd
|
|
40
|
+
*/
|
|
41
|
+
declare function resolveWorkspaceRoot(options?: ResolveWorkspaceRootOptions): Promise<WorkspaceRootResolution>;
|
|
42
|
+
|
|
43
|
+
export { type ResolveWorkspaceRootOptions, type WorkspaceRootResolution, resolveWorkspaceRoot };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { promises } from 'fs';
|
|
3
|
+
import { findRepoRoot } from '@kb-labs/core-sys';
|
|
4
|
+
|
|
5
|
+
// src/workspace/root-resolver.ts
|
|
6
|
+
var WORKSPACE_CONFIG_RELATIVE = path.join(".kb", "kb.config.json");
|
|
7
|
+
var defaultFs = {
|
|
8
|
+
async exists(target) {
|
|
9
|
+
try {
|
|
10
|
+
await promises.access(target);
|
|
11
|
+
return true;
|
|
12
|
+
} catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
function resolveEnvRoot(env) {
|
|
18
|
+
if (!env) {
|
|
19
|
+
return void 0;
|
|
20
|
+
}
|
|
21
|
+
return env.KB_LABS_WORKSPACE_ROOT ?? env.KB_LABS_REPO_ROOT;
|
|
22
|
+
}
|
|
23
|
+
async function findConfigRoot(startDir, fs) {
|
|
24
|
+
let current = path.resolve(startDir);
|
|
25
|
+
while (true) {
|
|
26
|
+
const configPath = path.join(current, WORKSPACE_CONFIG_RELATIVE);
|
|
27
|
+
if (await fs.exists(configPath)) {
|
|
28
|
+
return current;
|
|
29
|
+
}
|
|
30
|
+
const parent = path.dirname(current);
|
|
31
|
+
if (parent === current) {
|
|
32
|
+
return void 0;
|
|
33
|
+
}
|
|
34
|
+
current = parent;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async function resolveWorkspaceRoot(options = {}) {
|
|
38
|
+
const {
|
|
39
|
+
cwd,
|
|
40
|
+
env = process.env,
|
|
41
|
+
fs = defaultFs,
|
|
42
|
+
startDir = process.cwd()
|
|
43
|
+
} = options;
|
|
44
|
+
if (cwd) {
|
|
45
|
+
return {
|
|
46
|
+
rootDir: path.resolve(cwd),
|
|
47
|
+
source: "explicit"
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const envRoot = resolveEnvRoot(env);
|
|
51
|
+
if (envRoot) {
|
|
52
|
+
return {
|
|
53
|
+
rootDir: path.resolve(envRoot),
|
|
54
|
+
source: "env"
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
const configRoot = await findConfigRoot(startDir, fs);
|
|
58
|
+
if (configRoot) {
|
|
59
|
+
return {
|
|
60
|
+
rootDir: configRoot,
|
|
61
|
+
source: "config"
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
const repoRoot = await findRepoRoot(startDir);
|
|
66
|
+
const resolvedRepo = path.resolve(repoRoot);
|
|
67
|
+
const fsRoot = path.parse(resolvedRepo).root;
|
|
68
|
+
if (resolvedRepo !== fsRoot) {
|
|
69
|
+
return {
|
|
70
|
+
rootDir: resolvedRepo,
|
|
71
|
+
source: "repo"
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
} catch {
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
rootDir: path.resolve(startDir),
|
|
78
|
+
source: "fallback"
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export { resolveWorkspaceRoot };
|
|
83
|
+
//# sourceMappingURL=index.js.map
|
|
84
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kb-labs/core-workspace",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Core workspace utilities for KB Labs, including root directory resolution and workspace detection",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"sideEffects": false,
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@kb-labs/core-sys": "link:../core-sys"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@kb-labs/devkit": "link:../../../kb-labs-devkit",
|
|
26
|
+
"@types/node": "^24.3.3",
|
|
27
|
+
"@typescript-eslint/eslint-plugin": "^8",
|
|
28
|
+
"@typescript-eslint/parser": "^8",
|
|
29
|
+
"eslint": "^9",
|
|
30
|
+
"rimraf": "^6.0.1",
|
|
31
|
+
"tsup": "^8.5.0",
|
|
32
|
+
"typescript": "^5.6.3",
|
|
33
|
+
"vitest": "^3.2.4"
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=20.0.0",
|
|
37
|
+
"pnpm": ">=9.0.0"
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"clean": "rimraf dist",
|
|
44
|
+
"build": "pnpm clean && tsup --config tsup.config.ts",
|
|
45
|
+
"dev": "tsup --config tsup.config.ts --watch",
|
|
46
|
+
"type-check": "tsc --noEmit",
|
|
47
|
+
"test": "vitest run --passWithNoTests",
|
|
48
|
+
"lint": "eslint . --ignore-pattern 'dist/**'",
|
|
49
|
+
"lint:fix": "eslint . --fix --ignore-pattern 'dist/**'",
|
|
50
|
+
"test:watch": "vitest"
|
|
51
|
+
}
|
|
52
|
+
}
|