@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 ADDED
@@ -0,0 +1,2 @@
1
+ See root LICENSE file.
2
+
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: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
6
+ [![Node.js](https://img.shields.io/badge/Node.js-18.18.0+-green.svg)](https://nodejs.org/)
7
+ [![pnpm](https://img.shields.io/badge/pnpm-9.0.0+-orange.svg)](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
@@ -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
+ }